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

enum_module.png (25.38 KB, 下载次数: 0)
下载附件
函数代码
2025-9-22 09:44 上传
_BOOL8 __fastcall ExIsRestrictedCaller(char a1)
{
BOOLEAN v1; // bl
_BOOL8 result; // rax
struct _SECURITY_SUBJECT_CONTEXT SubjectSecurityContext; // [rsp+50h] [rbp-28h] BYREF
NTSTATUS AccessStatus; // [rsp+80h] [rbp+8h] BYREF
ACCESS_MASK GrantedAccess; // [rsp+88h] [rbp+10h] 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
如果ExIsRestrictedCaller返回一个true,则NtQuerySystemInformatio n系统调用返回一个0xC0000022,表示STATUS_ACCESS_DENIED。管理员启动一个进程的话,ExIsRestrictedCaller返回一个False,非管理员启动则返回一个True。

ExpQueryModuleInformation_win10_ida.png (84.36 KB, 下载次数: 0)
下载附件
IDA ExpQueryModuleInformation_win10
2025-9-22 09:47 上传

ExpQueryModuleInformation_win10_windbg.png (65.67 KB, 下载次数: 0)
下载附件
windbg ExpQueryModuleInformation_win10
2025-9-22 09:47 上传
从PsLoadModuleList链表中遍历已经加载的内核模块,提取信息赋值给NtQuerySystemInformation的第二个参数SystemInformation,它是一个PRTL_PROCESS_MODULES结构体。该结构体的内容如下:
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[256];
} RTL_PROCESS_MODULE_INFORMATION, * PRTL_PROCESS_MODULE_INFORMATION;
typedef struct _RTL_PROCESS_MODULES
{
ULONG NumberOfModules;
RTL_PROCESS_MODULE_INFORMATION Modules[1];
}
//
总结:在win10环境下,只要使用管理员启动进程来调用NtQuerySyst emInformation系统调用即可获取所有内核模块的的信息,包括基地址。
Win11环境
Win11下NtQuerySystemInformation系统调用的执行和win10相似,主要在ExIsRestrictedCaller和ExpQueryModuleInformation函数中有些不同。
__int64 __fastcall ExIsRestrictedCaller(KPROCESSOR_MODE a1, _DWORD *a2)
{
unsigned int v2; // edi
BOOLEAN v5; // bl
struct _SECURITY_SUBJECT_CONTEXT SubjectContext; // [rsp+50h] [rbp-28h] BYREF
NTSTATUS AccessStatus; // [rsp+80h] [rbp+8h] BYREF
ACCESS_MASK GrantedAccess; // [rsp+88h] [rbp+10h] 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
如果拥有SeDebugPrivilege权限,将a2指针处设置为False,否则为True。从Feature_RestrictKernelAddressLeaks__private_IsEnabled DeviceUsageNoInline这个符号名也可以看出,此处是为了限制非SeDebugPrivilege权限的进程获取内核模块的基地址的。
__int64 __fastcall ExpQueryModuleInformation(int a1, _DWORD *a2, unsigned int a3, _DWORD *a4)
{
__int64 result; // rax
unsigned int v8; // ecx
_QWORD v9[2]; // [rsp+30h] [rbp-38h] BYREF
unsigned int v10; // [rsp+40h] [rbp-28h]
int v11; // [rsp+44h] [rbp-24h]
_DWORD *v12; // [rsp+48h] [rbp-20h]
int v13; // [rsp+50h] [rbp-18h]
int v14; // [rsp+54h] [rbp-14h]
v9[0] = 0i64;
v13 = a1;
v14 = 0;
v12 = a4;
v11 = 8;
v9[1] = a2 + 2;
v10 = a3;
// v9是一个结构体,
result = MmEnumerateSystemImagesShared(ExpQueryModuleInformationImage, v9);
v8 = 0xC0000004;
if ( (int)(result + 0x80000000) = 8 )
{
*a2 = v14;
return LODWORD(v9[0]);
}
return v8;
}
return result;
}
__int64 __fastcall MmEnumerateSystemImagesShared(__int64 a1, __int64 a2)
{
return MiEnumerateSystemImages(a1, a2, 2i64);
}
// 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
这个函数就是遍历PsLoadedModuleList链表中的内核模块,每一个模块都调用一次ExpQueryModuleInformationImage函数。ExpQueryModuleInformationImage函数伪代码如下:

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

SeDebugPrivilege.png (196.71 KB, 下载次数: 0)
下载附件
SeDebugPrivilege
2025-9-22 09:53 上传
而问题的真正原因呢,还是在内核,如果不逆向分析内核,无法得出定位出。
破解程序代码
bool GetDebugPrivilege()
{
HANDLE hToken;
LUID luid;
TOKEN_PRIVILEGES tokenPrivileges;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
{
std::cerr
// 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[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;
}
getSbieDrvBaseAddress获取SbieDrv内核模块的基地址
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变量的内核地址
第三步:修改变量的内容
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一遍操作所需的权限,不管有没有已经获得。