阅读:3928回复:0
Mac OS X x64 环境下覆盖objective-c类结构并通过objc_msgSend获得RIP执行shellcode
author vvun91e0n
◆0 前言 阅读学习国外nemo大牛《Modern Objective-C Exploitation Techniques》文章的内容,就想在最新的OS X版本上调试出作者给出的代码。控制rip。我根据自己的调试,修改了原程序,才调试成功。对大牛原程序的部分代码的意图和计算方法难免理解不足,欢迎留言与我交流学习。本文主要简要介绍下对objective-c类的覆盖到控制rip的技术。是目前OS X平台下,比较主流的一种溢出利用方式。 ◆1 64位汇编知识及lldb简单调试命令 汇编知识主要是在调试的时候使用,在64位平台下调试必不可少的知识。对此熟悉的读者可直接跳过。下面简要介绍下64位汇编。 RIP的就是64位的指令寄存器。 通用64位寄存器 RAX RBX RCX RDX RBP RSI RDI RSPR8 --- R15 可以使用 [*]EAX 访问RAX的低32bits [*]AX 访问RAX的低16bits [*]AL 访问RAX的低8bist [*]AH 访问RAX低16bits的高8bits x86-64 Function Calling convention: [*]If the class is MEMORY, pass the argument on the stack. [*]If the class is INTEGER, the next available register of the sequence %rdi, %rsi, %rdx, %rcx, %r8 and %r9 is used. lldb是苹果公司推出的用以替代gdb的调试器。随xcode一起安装。是进行动态调试的利器。调试时必不可少。下面简要介绍下会用到的一些基础命令: 在命令行输入lldb,就会进入调试工具 (lldb)help会显示所有的命令,需要详细了解可以用输入help + 命令查询 file命令加载需要调试的程序 (lldb) file /Users/vvun91e0n/Desktop/OC64exploitCurrent executable set to '/Users/vvun91e0n/Desktop/OC64exploit' (x86_64).breakpoint set用来设置断点 (lldb) breakpoint set --name lengthbreakpoint list可以用来查看所有断点 breakpoint disable 关闭断点 breakpoint enable 激活断点 ni 单步步不执行指令 run或r启动进程进行调试 register read 读取现在所有寄存器的值 想读取特定的几个寄存器,写在后面就行,使用简化命令如 (lldb) re r rdi r10 rsi rdi = ◆000000100202fa0 r10 = ◆000000000000001 rsi = ◆0007fff907bb509memory read 可以读取指定地址的内存数据,如下指定地址就是length字符串。也可以使用gdb风格的x ◆0007fff907bb509命令来读取内存数据。 (lldb) memory read ◆0007fff907bb5090x7fff907bb509: 6c 65 6e 67 74 68 00 69 73 54 79 70 65 4e 6f 74 length.isTypeNot0x7fff907bb519: 45 78 63 6c 75 73 69 76 65 3a 00 61 70 70 65 6e Exclusive:.appen(lldb) x ◆0007fff907bb5090x7fff907bb509: 6c 65 6e 67 74 68 00 69 73 54 79 70 65 4e 6f 74 length.isTypeNot0x7fff907bb519: 45 78 63 6c 75 73 69 76 65 3a 00 61 70 70 65 6e Exclusive:.appencontinue 或者c 命令来继续执行。 kill来结束进程。run来重新启动。 其他如条件断点,修改寄存器值等命令读者可以使用help命令了解。 ◆2 objective-c方法调用 你需要对objective-c语言有一定的基本的了解。特别是objc_msgSend函数的调用机制。可以参考:《The Objective-C Runtime: Understanding and Abusing》。这篇文章是nemo2009年发表在phrack上的。在32位系统基础上讲Objective-C Runtime溢出的文章。还是有阅读价值的。特别是对后面64位溢出的理解。 下面简要介绍下objective-c,先看看一个简单的类实现和调用: #!c// Talker.h#import @interface Talker : NSObject //定义一个类- (void) say: (char *) str; //声明一个say方法@end// Talker.m#import "Talker.h"@implementation Talker //类的相关方法实现- (void) say: (char *) phrase{ printf("%sn",phrase);}@end//main.m#import "Talker.h"int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... Talker *talker = [[Talker alloc] init];//分配内存,初始化 [talker say: "Hello, World!"]; //调用say方法 } return 0;}如上面代码所示,定义了一个Talker类,并在main函数中调用了say方法。 可以看出objective-c语言的方法调用语法为 [ : ]; 主程序进行了3次函数调用 class | method-------|-----------Talker | alloctalker | inittalker | say 将上面的代码在xcode中编译生成后的程序放入Hopper静态反汇编后,会得到一下汇编代码(注:这个事release版的结果,debug版的反汇编结果稍有不同) #!bashmov rdi, qword [ds:objc_cls_ref_Talker] ; objc_cls_ref_Talker, argument "instance" for method _objc_msgSendmov rsi, qword [ds:0x1000010f8] ; @selector(alloc), argument "selector" for method _objc_msgSendmov r15, qword [ds:imp___got__objc_msgSend] ; imp___got__objc_msgSendcall r15 ; _objc_msgSendmov rsi, qword [ds:0x100001100] ; @selector(init), argument "selector" for method _objc_msgSendmov rdi, rax ; argument "instance" for method _objc_msgSendcall r15 ; _objc_msgSendmov rbx, raxmov rsi, qword [ds:0x100001108] ; @selector(say:), argument "selector" for method _objc_msgSendlea rdx, qword [ds:0x100000f70] ; "Hello, World!"mov rdi, rbx ; argument "instance" for method _objc_msgSendcall r15 ; _objc_msgSend 可以看出在进行方法调用时并不是直接call方法地址,而是将方法的selector作为第二个参数传入rsi寄存器。与之前介绍的64位汇编调用约定一样。第一参数对象指针isa压入rdi。然后call r15调用objc_msgSend函数。这个函数就是需要研究利用的对象。相对于c语言在编译时就决定运行时所调用的函数的静态绑定。objectie-c语言利用了消息传递objc_msgSend函数在运行时,动态绑定调用函数。objective-c利用这个消息机制实现了runtime的方法调用。提高了语言的灵活性。使得objective-c成为了一门动态语言。 ◆3 objective-c的类结构和objc_msgSend函数 在64位系统中,runtime进行了重新编写,类的实现由原来的c语言struct结构,变成了c++类。 struct objc_class : objc_object {// Class ISA;Class superclass;cache_t cache; // formerly cache pointer and vtableclass_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags…} 类的第一个变量指向父类,在查询不到需要调用的方法时,会向父类继续进行查询。 第二个变量指向cache,这个cache缓存了最近调用方法的selector和imp(方法入口地址),之所以使用cache就是为了提高objective-c语言的动态绑定特性的效率。加快查询速度。 struct cache_t {struct bucket_t *_buckets;mask_t _mask;mask_t _occupied;…}struct bucket_t {private:cache_key_t _key;IMP _imp;...} cache的结构如上,cache里面的buckets指向一个bucket类,类里面的_key就是selector,指向选择子的字符串。_imp指向方法地址。 其中需要说明的是_mask。这个mask用来和输入的sel进行按位与,对结果进行处理,作为buckets数组的序号。加快选择速度,确定范围。在我们的实验代码里,根据调试发现mask实际上起到一个确定遍历范围的作用。 我的程序在mask的选择上,是根据我自己的调试结果设置的。nemo的源码的mask设置在我机器上运行后会取到非法内存。不知道是不是版本改变了更新了还是什么其他的原因导致的。我调试的时候lldb反汇编了objc_msgSend函数,对照该函数的源代码进行了调试。对nemo的代码进行了修改。读者可以同时看看nemo的源代码。还有几处我都做出了修改。 查询Objective-C Runtime Reference,可以得到该函数声明如下 #!bashid objc_msgSend ( id self, SEL op, ... );Parameters:self A pointer that points to the instance of the class that is to receive the message.op The selector of the method that handles the message.... A variable argument list containing the arguments to the method. 该函数的源代码是开源的,由汇编语言实现,以提高objective-c的执行效率。 大家看到第一个self参数实际就是指向实例的指针。第二个op参数就是sel,就是选择子。指向方法名字符串。 关于objc_msgSend的汇编源代码可以到苹果公司网站查看。以下是目前最新版本的链接地址 http://www.opensource.apple.com/source/objc4/objc4-646/runtime/Messengers.subproj/objc-msg-x86_64.s 下面给出源代码 #!c++/******************************************************************** * * id objc_msgSend(id self, SEL _cmd,...); * ********************************************************************/ .data .align 3 .globl _objc_debug_taggedpointer_classes_objc_debug_taggedpointer_classes: .fill 16, 8, 0 ENTRY _objc_msgSend MESSENGER_START NilTest NORMAL GetIsaFast NORMAL // r11 = self->isa CacheLookup NORMAL // calls IMP on success NilTestSupport NORMAL GetIsaSupport NORMAL// cache miss: go search the method listsLCacheMiss: // isa still in r11 MethodTableLookup %a1, %a2 // r11 = IMP cmp %r11, %r11 // set eq (nonstret) for forwarding jmp *%r11 // goto *imp END_ENTRY _objc_msgSend ENTRY _objc_msgSend_fixup int3 END_ENTRY _objc_msgSend_fixup STATIC_ENTRY _objc_msgSend_fixedup // Load _cmd from the message_ref movq 8(%a2), %a2 jmp _objc_msgSend END_ENTRY _objc_msgSend_fixedup 可以看到该函数先获取isa指针,然后调用Cachelookup,在Cache中寻找sel,如果找不到就调用MethodTableLookup,在MethodTable中继续查找sel。这样只要我们能够覆盖类的内存,构造虚假的cache,提供正确的sel,就能够最终控制rip。对类结构进行溢出覆盖控制也是现在比较流行的OS X下溢出利用技术。 下面再来看看CacheLookup的源代码: #!bash.macro CacheLookup.if $0 != STRET && $0 != SUPER_STRET && $0 != SUPER2_STRET movq %a2, %r10 // r10 = _cmd.else movq %a3, %r10 // r10 = _cmd.endif andl 24(%r11), %r10d // r10 = _cmd & class->cache.mask shlq $$4, %r10 // r10 = offset = (_cmd & mask)sel != _cmd).else cmpq (%r10), %a3 // if (bucket->sel != _cmd).endif jne 1f // scan more // CacheHit must always be preceded by a not-taken `jne` instruction CacheHit $0 // call or return imp1: // loop cmpq $$0, (%r10) je LCacheMiss_f // if (bucket->sel == 0) cache miss cmpq 16(%r11), %r10 je 3f // if (bucket == cache->buckets) wrap subq $$16, %r10 // bucket--2:.if $0 != STRET && $0 != SUPER_STRET && $0 != SUPER2_STRET cmpq (%r10), %a2 // if (bucket->sel != _cmd).else cmpq (%r10), %a3 // if (bucket->sel != _cmd).endif jne 1b // scan more // CacheHit must always be preceded by a not-taken `jne` instruction CacheHit $0 // call or return imp其中的CacheHit就是在sel匹配成功后跳转到imp,从中可以看出mask的具体使用方法。 andl 24(%r11), %r10d // r10 = _cmd & class->cache.mask shlq $$4, %r10 // r10 = offset = (_cmd & mask) |
|
|