深入XPC:逆向分析XPC对象
栏目: Objective-C · 发布时间: 6年前
内容简介:最近我在FortiGuard实验室一直在深入研究macOS系统安全,主要关注的是发现和分析IPC漏洞方面内容。在本文中,我将与大家分享XPC内部数据类型,可以帮助研究人员(包括我自己)快速分析XPC漏洞根源,也能深入分析针对这些漏洞的利用技术。XPC是macOS/iOS系统上使用的增强型IPC框架,自10.7/5.0版引入以来,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
提供自己的数据类型,具体数据类型如下所示:
图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
函数被多次引用。
图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);
图3. python脚本输出结果,显示XPC对象大小
此时我们已经知道所有不同数据类型的XPC对象的大小。接下来我们可以看一下 _xpc_base_create
函数的实现。
图4. _xpc_base_create
函数的实现
可以看到XPC对象的实际大小等于 Size
参数+ 0x18
。
然后我们需要进行一些逆向分析工作,检查所有对象的内存布局。在本文中,我想与大家分享主要类型的分析过程,其他类型会在后续文章中详细介绍。
二、主要类型分析
xpc_int64_t
我们可以使用 xpc_int64_create
函数来创建一个 xpc_int64_t
对象,如下所示:
使用 LLDB
观察 xpc_int64_t
对象的内存布局:
xpc_uint64_t
对象的结构如下所示:
图5. xpc_uint64_t
结构
xpc_uint64_t
使用 xpc_uint64_create
函数创建 xpc_uint64_t
对象,代码如下:
可以看到返回值不是有效的内存地址。我们需要在输入参数上执行一些算数运算来生成返回值。在这个例子中,XPC直接使用64位 unsigned integer
来表示 xpc_uint64_t
对象。
创建 xpc_uint64_t
对象的另一个例子如下:
在 LLDB
中 xpc_uint64_t
对象的内存布局如下所示:
可以看到返回值指向的内存缓冲区对应的是 xpc_uint64_t
对象,且输入参数位于 0x18
偏移地址处。
接下来我们可以深入分析 xpc_uint64_create
函数的具体实现,如下所示:
图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_mask
及 objc_debug_taggedpointer_obfuscator
的值,如下所示:
一旦我们知道 objc_debug_taggedpointer_obfuscator
的值,我们就可以计算出返回值。
每个新进程实例所对应的 objc_debug_taggedpointer_obfuscator
都为随机值。现在我们可以跟踪一下这个变量的生成过程。
可以看到, objc_debug_taggedpointer_obfuscator
实际上是 libobjc.A.dylib
库中的一个全局变量。如下代码(源文件: objc4-750/runtime/objc-runtime-new.mm
)可以用来生成随机的 objc_debug_taggedpointer_obfuscator
:
图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
对象的结构,如下所示:
图8. xpc_uint64_t
对象结构
xpc_uuid_t
我们可以使用 xpc_uuid_create
函数来创建 xpc_uuid_t
对象(UUID为universally unique identifier的缩写),如下所示:
在 LLDB
中查看 xpc_uuid_t
对象的内存布局,如下所示:
根据内存布局信息,我们可以轻松澄清 xpc_uuid_t
对象的结构:
图9. xpc_uuid_t
对象结构
xpc_double_t
我们可以使用 xpc_double_create
函数来创建 xpc_double_t
对象,如下所示:
在 LLDB
中查看 xpc_double_t
对象的内存布局:
xpc_double_t
对象的结构如下所示:
图10. xpc_double_t
对象结构
xpc_date_t
我们可以使用 xpc_date_create
函数来创建 xpc_date_t
对象,如下所示:
在 LLDB
中查看 xpc_date_t
对象的内存结构:
xpc_date_t
对象的结构如下所示:
图11. xpc_date_t
对象结构
xpc_string_t
可以使用 xpc_string_create
函数创建 xpc_string_t
对象,如下所示:
在 LLDB
中查看 xpc_string_t
对象的内存布局:
xpc_string_t
对象的结构如下所示:
图12. xpc_string_t
对象结构
xpc_array_t
可以使用 xpc_array_create
函数创建 xpc_array_t
对象,如下所示:
在这个例子中,我们首先创建了一个 xpc_array_t
对象,然后将3个值加入数组中。 xpc_array_create
函数声明如下:
xpc_array_create
函数的实现如下所示:
图13. xpc_array_create
函数实现代码
从上图中,我们可知数组的大小等于 (count*2+0x08)
,这个值存放在 0x1c
偏移处(4字节大小)。指向已分配缓冲区的指针存放于 0x20
偏移处,已分配缓冲区的大小等于 (count*2+0x8)*0x8
。
在 LLDB
中观察该对象的内存布局,如下所示:
数组的长度存放于 0x18
偏移处(4字节)。 0x20
偏移处的指针指向的是已分配的 xpc_object_t
缓冲区,缓冲区中存放的是数组中的所有元素( xpc_object_t
)。 xpc_array_t
对象的结构如下所示:
图14. xpc_array_t
对象结构
xpc_data_t
可以使用 xpc_data_create
函数创建 xpc_data_t
对象,如下所示:
在 LLDB
中观察 xpc_data_t
对象的内存布局:
xpc_data_t
对象的结构如下图所示:
图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
对象,如下所示。
在 LLDB
中观察 xpc_dictionary_t
对象的内存布局。
hash_buckets
字段是长度为 7
的一个数组, hash_buckets[7]
中的每个元素存放的是XPC字典链表项。比如, hash_buckets[3]
的内存布局如下所示:
可以确定XPC字典链表项的结构如下所示:
图16. XPC字典链表项结构
最后,我们再给出 xpc_dictionary_t
对象的结构,如下所示。
目前我们已经讨论了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
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- [译] 深入 JS 对象属性
- 深入理解Objective-C中实例、类对象、元类对象之间的关系
- 深入理解Python面向对象的三大特性
- <<深入PHP面向对象、模式与实践>>读书笔记:面向对象设计和过程式编程
- python面向对象-2深入类的属性
- 深入了解Python字符串对象的实现
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
ANSI Common Lisp
Paul Graham / Prentice Hall / 1995-11-12 / USD 116.40
For use as a core text supplement in any course covering common LISP such as Artificial Intelligence or Concepts of Programming Languages. Teaching students new and more powerful ways of thinking abo......一起来看看 《ANSI Common Lisp》 这本书的介绍吧!