深入XPC:逆向分析XPC对象

栏目: Objective-C · 发布时间: 5年前

内容简介:最近我在FortiGuard实验室一直在深入研究macOS系统安全,主要关注的是发现和分析IPC漏洞方面内容。在本文中,我将与大家分享XPC内部数据类型,可以帮助研究人员(包括我自己)快速分析XPC漏洞根源,也能深入分析针对这些漏洞的利用技术。XPC是macOS/iOS系统上使用的增强型IPC框架,自10.7/5.0版引入以来,XPC的使用范围已经呈爆炸式增长。XPC依然包含没有官方说明文档的大量功能,具体实现也没有公开(例如,

深入XPC:逆向分析XPC对象

一、前言

最近我在FortiGuard实验室一直在深入研究macOS系统安全,主要关注的是发现和分析IPC漏洞方面内容。在本文中,我将与大家分享XPC内部数据类型,可以帮助研究人员(包括我自己)快速分析XPC漏洞根源,也能深入分析针对这些漏洞的利用技术。

XPC是macOS/iOS系统上使用的增强型IPC框架,自10.7/5.0版引入以来,XPC的使用范围已经呈爆炸式增长。XPC依然包含没有官方说明文档的大量功能,具体实现也没有公开(例如, libxpc 这个主工程为闭源项目)。XPC在两个层面上开放API:底层以及 Foundation 封装层。在本文中我们只关注底层API,这些API为 libxpc.dylib 直接导出的 xpc_* 函数。

这些API可以分为object API以及transport API。XPC通过 libxpc.dylib 提供自己的数据类型,具体数据类型如下所示:

深入XPC:逆向分析XPC对象

图1. XPC提供的数据类型

从C API角度来看,所有的对象实际上都是 xpc_object_t 。实际类型可以通过 xpc_get_type(xpc_object_t) 函数动态确定。所有数据类型可以使用对应的 xpc_objectType_create 函数创建,并且所有这些函数都会调用 _xpc_base_create(Class, Size) 函数,其中 Size 参数指定了对象的大小,而 Class 参数为某个 _OS_xpc_type_* 元类( metaclass )。

我们可以通过Hopper Disassembler v4看到 _xpc_base_create 函数被多次引用。

深入XPC:逆向分析XPC对象

图2. 对 _xpc_base_create 函数的引用代码

我开发了Hopper的一个 python 脚本,可以自动找出调用 _xpc_base_create 函数时所使用的具体参数。如下python脚本可以显示Hopper Disassembler中XPC对象的大小。

def get_last2instructions_addr(seg, x):
                  last1ins_addr = seg.getInstructionStart(x - 1)
                  last2ins_addr = seg.getInstructionStart(last1ins_addr - 1)
                  last2ins = seg.getInstructionAtAddress(last2ins_addr)
                  last1ins = seg.getInstructionAtAddress(last1ins_addr)
                  print hex(last2ins_addr), last2ins.getInstructionString(), last2ins.getRawArgument(0), last2ins.getRawArgument(1)
                  print hex(last1ins_addr), last1ins.getInstructionString(), last1ins.getRawArgument(0), last1ins.getRawArgument(1)
                  return last2ins,last1ins
def run():
                  print '[*] Demonstrating XPC ojbect sizes using a hopper diassembler's python script'
                  xpc_object_sizes_dict = dict()
                  doc = Document.getCurrentDocument()
                  _xpc_base_create_addr = doc.getAddressForName('__xpc_base_create')
                  for i in range(doc.getSegmentCount()):
                                    seg = doc.getSegment(i)
                                    #print '[*]'+ seg.getName()
                                    if('__TEXT' == seg.getName()):
                                                      eachxrefs = seg.getReferencesOfAddress(_xpc_base_create_addr)
                                                      for x in eachxrefs:
                                                                        last2ins,last1ins = get_last2instructions_addr(seg,x)
                                                                        p = seg.getProcedureAtAddress(x)
                                                                        p_entry_addr =  p.getEntryPoint()
                                                                        pname = seg.getNameAtAddress(p_entry_addr)
                                                                        x_symbol = pname + '+' + hex(x - p_entry_addr)
                                                                        print hex(x),'(' + x_symbol + ')'
                                                                        ins0 = seg.getInstructionAtAddress(x - 5)
                                                                        ins1 = seg.getInstructionAtAddress(x - 12)
                                                                        if last2ins.getInstructionString() == 'mov' and last1ins.getInstructionString() == 'lea':
                                                                                          if last2ins.getRawArgument(0) == 'esi' and last1ins.getRawArgument(0) == 'rdi':
                                                                                                            indirect_addr = int(last1ins.getRawArgument(1)[7:-1],16)
                                                                                                            xpcObj_len = last2ins.getRawArgument(1)
                                                                                                            callerinfo = '__xpc_base_create('+ doc.getNameAtAddress(indirect_addr)+',' + xpcObj_len+ ');'
                                                                                                            if callerinfo not in xpc_object_sizes_dict.keys():
                                                                                                                              xpc_object_sizes_dict[callerinfo] = '#from ' + x_symbol
                                                                                                            else:
                                                                                                                              xpc_object_sizes_dict[callerinfo] = xpc_object_sizes_dict[callerinfo] + ',' + x_symbol
                                                                                                            print callerinfo
                                                                                                            #xpc_object_sizes_list.append(callerinfo)
                                                                        elif last2ins.getInstructionString() == 'lea' and last1ins.getInstructionString() == 'mov':
                                                                                          if last2ins.getRawArgument(0) == 'rdi' and last1ins.getRawArgument(0) == 'esi':
                                                                                                            indirect_addr = int(last2ins.getRawArgument(1)[7:-1],16)
                                                                                                            xpcObj_len = last1ins.getRawArgument(1)
                                                                                                            callerinfo = '__xpc_base_create('+ doc.getNameAtAddress(indirect_addr)+',' + xpcObj_len+ ');'
                                                                                                            if callerinfo not in xpc_object_sizes_dict.keys():
                                                                                                                              xpc_object_sizes_dict[callerinfo] = '#from ' + x_symbol

                                                                                                            else:
                                                                                                                              xpc_object_sizes_dict[callerinfo] = xpc_object_sizes_dict[callerinfo] + ',' + x_symbol
                                                                                                            print callerinfo
                                                                                                            #xpc_object_sizes_list.append(callerinfo)
                                                                        elif last2ins.getInstructionString() == 'lea' and last1ins.getInstructionString() == 'lea':
                                                                                          if last2ins.getRawArgument(0) == 'rsi' and last1ins.getRawArgument(0) == 'rdi':
                                                                                                            indirect_addr = int(last1ins.getRawArgument(1)[7:-1],16)
                                                                                                            xpcObj_len = last2ins.getRawArgument(1)[7:-1]
                                                                                                            callerinfo = '__xpc_base_create('+ doc.getNameAtAddress(indirect_addr)+',' + xpcObj_len+ ');'
                                                                                                            if callerinfo not in xpc_object_sizes_dict.keys():
                                                                                                                              xpc_object_sizes_dict[callerinfo] = '#from ' + x_symbol
                                                                                                            else:
                                                                                                                              xpc_object_sizes_dict[callerinfo] = xpc_object_sizes_dict[callerinfo] + ',' + x_symbol
                                                                                                            print callerinfo
                                                                                                            #xpc_object_sizes_list.append(callerinfo)
                                                                                          elif last2ins.getRawArgument(0) == 'rdi' and last1ins.getRawArgument(0) == 'rsi':
                                                                                                            indirect_addr = int(last2ins.getRawArgument(1)[7:-1],16)
                                                                                                            xpcObj_len = last1ins.getRawArgument(1)[7:-1]
                                                                                                            callerinfo = '__xpc_base_create('+ doc.getNameAtAddress(indirect_addr)+',' + xpcObj_len+ ');'
                                                                                                            if callerinfo not in xpc_object_sizes_dict.keys():
                                                                                                                              xpc_object_sizes_dict[callerinfo] = '#from ' + x_symbol
                                                                                                            else:
                                                                                                                              xpc_object_sizes_dict[callerinfo] = xpc_object_sizes_dict[callerinfo] + ',' + x_symbol
                                                                                                            print callerinfo
                                                                                                            #xpc_object_sizes_list.append(callerinfo)
                                                                        print '____________________________________________________________'
                  dict_len = len(xpc_object_sizes_dict)
                  print '[*] Total of XPC object: %d' % dict_len
                  for key in xpc_object_sizes_dict.keys():
                                    print key, xpc_object_sizes_dict[key]
if __name__ == '__main__':
                  run()

运行该脚本后,我们可以看到所有XPC对象大小,如下所示:

__xpc_base_create(_OBJC_CLASS_$_OS_xpc_serializer,0x98);
__xpc_base_create(_OBJC_CLASS_$_OS_xpc_mach_send,0x8);
__xpc_base_create(_OBJC_CLASS_$_OS_xpc_activity,0x78);
__xpc_base_create(_OBJC_CLASS_$_OS_xpc_data,0x28);
__xpc_base_create(_OBJC_CLASS_$_OS_xpc_double,0x8);
__xpc_base_create(_OBJC_CLASS_$_OS_xpc_file_transfer,0x48);
__xpc_base_create(_OBJC_CLASS_$_OS_xpc_service_instance,0x78);
__xpc_base_create(_OBJC_CLASS_$_OS_xpc_uint64,0x8);
__xpc_base_create(_OBJC_CLASS_$_OS_xpc_bundle,0x238);
__xpc_base_create(_OBJC_CLASS_$_OS_xpc_pointer,0x8);
__xpc_base_create(_OBJC_CLASS_$_OS_xpc_string,0x10);
__xpc_base_create(_OBJC_CLASS_$_OS_xpc_pipe,r12+0x20);
__xpc_base_create(_OBJC_CLASS_$_OS_xpc_connection,r14+0xa8);
__xpc_base_create(_OBJC_CLASS_$_OS_xpc_shmem,0x18);
__xpc_base_create(_OBJC_CLASS_$_OS_xpc_dictionary,0xa8);
__xpc_base_create(_OBJC_CLASS_$_OS_xpc_uuid,0x10);
__xpc_base_create(_OBJC_CLASS_$_OS_xpc_connection,0xa8);
__xpc_base_create(_OBJC_CLASS_$_OS_xpc_endpoint,0x8);
__xpc_base_create(_OBJC_CLASS_$_OS_xpc_int64,0x8);
__xpc_base_create(_OBJC_CLASS_$_OS_xpc_date,0x8);
__xpc_base_create(_OBJC_CLASS_$_OS_xpc_fd,0x8);
__xpc_base_create(_OBJC_CLASS_$_OS_xpc_mach_recv,0x10);
__xpc_base_create(_OBJC_CLASS_$_OS_xpc_bool,0x8);
__xpc_base_create(_OBJC_CLASS_$_OS_xpc_array,0x10);
__xpc_base_create(_OBJC_CLASS_$_OS_xpc_service,0x5d);

深入XPC:逆向分析XPC对象

图3. python脚本输出结果,显示XPC对象大小

此时我们已经知道所有不同数据类型的XPC对象的大小。接下来我们可以看一下 _xpc_base_create 函数的实现。

深入XPC:逆向分析XPC对象

图4. _xpc_base_create 函数的实现

可以看到XPC对象的实际大小等于 Size 参数+ 0x18

然后我们需要进行一些逆向分析工作,检查所有对象的内存布局。在本文中,我想与大家分享主要类型的分析过程,其他类型会在后续文章中详细介绍。

二、主要类型分析

xpc_int64_t

我们可以使用 xpc_int64_create 函数来创建一个 xpc_int64_t 对象,如下所示:

深入XPC:逆向分析XPC对象

使用 LLDB 观察 xpc_int64_t 对象的内存布局:

深入XPC:逆向分析XPC对象

xpc_uint64_t 对象的结构如下所示:

深入XPC:逆向分析XPC对象

图5. xpc_uint64_t 结构

xpc_uint64_t

使用 xpc_uint64_create 函数创建 xpc_uint64_t 对象,代码如下:

深入XPC:逆向分析XPC对象

可以看到返回值不是有效的内存地址。我们需要在输入参数上执行一些算数运算来生成返回值。在这个例子中,XPC直接使用64位 unsigned integer 来表示 xpc_uint64_t 对象。

深入XPC:逆向分析XPC对象

创建 xpc_uint64_t 对象的另一个例子如下:

深入XPC:逆向分析XPC对象

LLDBxpc_uint64_t 对象的内存布局如下所示:

深入XPC:逆向分析XPC对象

可以看到返回值指向的内存缓冲区对应的是 xpc_uint64_t 对象,且输入参数位于 0x18 偏移地址处。

接下来我们可以深入分析 xpc_uint64_create 函数的具体实现,如下所示:

深入XPC:逆向分析XPC对象

图6. _xpc_uint64_create 函数具体实现

在该函数中,代码首先会将参数逻辑右移52位。

a) 如果结果不等于 0 ,则会调用 _xpc_base_create 函数来创建XPC对象,然后将 0x08 (4字节长)写入 0x14 偏移处的缓冲区。最后,代码将参数(8字节长)写入 0x18 偏移处的缓冲区。

b) 如果结果等于 0 且全局变量 objc_debug_taggedpointer_mask 不等于 0 ,那么就会执行 (value << 0xc | 0x4f) ^ objc_debug_taggedpointer_obfuscator 。在 LLDB 调试器中,我们可以看到 objc_debug_taggedpointer_obfuscator 变量等于 0x5de9b03e5c731aae ,因此运算结果会等于 0x5de9b42a48670ae1 ,这个值即为 _xpc_uint64_create 函数的返回值。如果结果为 0 ,那么就与 a) 情况相同。

我们可以检查全局变量 objc_debug_taggedpointer_maskobjc_debug_taggedpointer_obfuscator 的值,如下所示:

深入XPC:逆向分析XPC对象

一旦我们知道 objc_debug_taggedpointer_obfuscator 的值,我们就可以计算出返回值。

深入XPC:逆向分析XPC对象

每个新进程实例所对应的 objc_debug_taggedpointer_obfuscator 都为随机值。现在我们可以跟踪一下这个变量的生成过程。

深入XPC:逆向分析XPC对象

可以看到, objc_debug_taggedpointer_obfuscator 实际上是 libobjc.A.dylib 库中的一个全局变量。如下代码(源文件: objc4-750/runtime/objc-runtime-new.mm )可以用来生成随机的 objc_debug_taggedpointer_obfuscator :

深入XPC:逆向分析XPC对象

图7. 初始化 objc_debug_taggedpointer_obfuscator 变量

可以使用 void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) 函数完成初始化工作,具体参考 objc-runtime-new.mm 中的源代码。在二进制镜像的初始化阶段中,我们还可以看到随机化的 objc_debug_taggedpointer_obfuscator 全局变量生成过程。

最后我们再给出 xpc_uint64_t 对象的结构,如下所示:

深入XPC:逆向分析XPC对象

图8. xpc_uint64_t 对象结构

xpc_uuid_t

我们可以使用 xpc_uuid_create 函数来创建 xpc_uuid_t 对象(UUID为universally unique identifier的缩写),如下所示:

深入XPC:逆向分析XPC对象

LLDB 中查看 xpc_uuid_t 对象的内存布局,如下所示:

深入XPC:逆向分析XPC对象

根据内存布局信息,我们可以轻松澄清 xpc_uuid_t 对象的结构:

深入XPC:逆向分析XPC对象

图9. xpc_uuid_t 对象结构

xpc_double_t

我们可以使用 xpc_double_create 函数来创建 xpc_double_t 对象,如下所示:

深入XPC:逆向分析XPC对象

LLDB 中查看 xpc_double_t 对象的内存布局:

深入XPC:逆向分析XPC对象

xpc_double_t 对象的结构如下所示:

深入XPC:逆向分析XPC对象

图10. xpc_double_t 对象结构

xpc_date_t

我们可以使用 xpc_date_create 函数来创建 xpc_date_t 对象,如下所示:

深入XPC:逆向分析XPC对象

LLDB 中查看 xpc_date_t 对象的内存结构:

深入XPC:逆向分析XPC对象

xpc_date_t 对象的结构如下所示:

深入XPC:逆向分析XPC对象

图11. xpc_date_t 对象结构

xpc_string_t

可以使用 xpc_string_create 函数创建 xpc_string_t 对象,如下所示:

深入XPC:逆向分析XPC对象

LLDB 中查看 xpc_string_t 对象的内存布局:

深入XPC:逆向分析XPC对象

xpc_string_t 对象的结构如下所示:

深入XPC:逆向分析XPC对象

图12. xpc_string_t 对象结构

xpc_array_t

可以使用 xpc_array_create 函数创建 xpc_array_t 对象,如下所示:

深入XPC:逆向分析XPC对象

在这个例子中,我们首先创建了一个 xpc_array_t 对象,然后将3个值加入数组中。 xpc_array_create 函数声明如下:

深入XPC:逆向分析XPC对象

xpc_array_create 函数的实现如下所示:

深入XPC:逆向分析XPC对象

图13. xpc_array_create 函数实现代码

从上图中,我们可知数组的大小等于 (count*2+0x08) ,这个值存放在 0x1c 偏移处(4字节大小)。指向已分配缓冲区的指针存放于 0x20 偏移处,已分配缓冲区的大小等于 (count*2+0x8)*0x8

LLDB 中观察该对象的内存布局,如下所示:

深入XPC:逆向分析XPC对象

数组的长度存放于 0x18 偏移处(4字节)。 0x20 偏移处的指针指向的是已分配的 xpc_object_t 缓冲区,缓冲区中存放的是数组中的所有元素( xpc_object_t )。 xpc_array_t 对象的结构如下所示:

深入XPC:逆向分析XPC对象

图14. xpc_array_t 对象结构

xpc_data_t

可以使用 xpc_data_create 函数创建 xpc_data_t 对象,如下所示:

深入XPC:逆向分析XPC对象

LLDB 中观察 xpc_data_t 对象的内存布局:

深入XPC:逆向分析XPC对象

xpc_data_t 对象的结构如下图所示:

深入XPC:逆向分析XPC对象

图15. xpc_data_t 对象结构

如果数据缓冲区的长度大于等于 0x4000 ,那么 0x14 偏移处的值则会等于 (length+0x7)&0xfffffffc ,否则就等于 0x04

xpc_dictionary_t

xpc_dictionary_t 类型在XPC中扮演着重要角色。端点间所有消息都以字典格式传递,这样序列化/反序列化处理起来更加方便。与其他主要类型相比, xpc_dictionary_t 的内部构造更为复杂。让我们一步一步揭开面纱。

可以使用 xpc_dictionary_create 函数创建 xpc_dictionary_t 对象,如下所示。

深入XPC:逆向分析XPC对象

LLDB 中观察 xpc_dictionary_t 对象的内存布局。

深入XPC:逆向分析XPC对象

hash_buckets 字段是长度为 7 的一个数组, hash_buckets[7] 中的每个元素存放的是XPC字典链表项。比如, hash_buckets[3] 的内存布局如下所示:

深入XPC:逆向分析XPC对象

可以确定XPC字典链表项的结构如下所示:

深入XPC:逆向分析XPC对象

图16. XPC字典链表项结构

最后,我们再给出 xpc_dictionary_t 对象的结构,如下所示。

深入XPC:逆向分析XPC对象

目前我们已经讨论了XPC对象的主要数据类型,也分析了这些对象的内部结构及内存布局。了解内部结构后,我们不仅能快速分析XPC中的漏洞,也能在跟踪和解析XPC相关漏洞利用技术中事半功倍。

三、调试环境

macOS Mojave version 10.14.1

需要注意的是,其他macOS版本上这些XPC对象结构可能有所不同。

四、参考资料

https://thecyberwire.com/events/docs/IanBeer_JSS_Slides.pdf

OS Internals, Volume I: User Mode by Jonathan Levin


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

百度SEM竞价推广

百度SEM竞价推广

马明泽 / 电子工业出版社 / 2017-5 / 59

竞价推广已成为企业昀主要的网络营销方式,《百度SEM竞价推广:策略、方法、技巧与实战》以百度竞价推广为基础,全面阐述了整个竞价推广过程中的重要环节,涉及大量账户操作实战技巧,以及解决各类难点的方法,其中包括搜索引擎营销基础、百度搜索推广介绍、账户结构搭建技巧、关键词与创意的使用技巧、质量度优化与提升、账户工具的使用、百度推广客户端的使用、企业搜索推广方案制作、百度网盟推广、着陆页分析、效果优化与数......一起来看看 《百度SEM竞价推广》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

URL 编码/解码
URL 编码/解码

URL 编码/解码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具