New issue
Advanced search Search tips
Note: Color blocks (like or ) mean that a user may not be available. Tooltip shows the reason.
Starred by 6 users
Status: Fixed
Owner:
Email to this user bounced
Closed: Oct 2014



Sign in to add a comment
OS X IOKit kernel code execution due to heap overflow in IOHIKeyboardMapper::parseKeyMapping
Project Member Reported by ianbeer@google.com, Jun 30 2014 Back to list
The function IOHIKeyboardMapper::parseKeyMapping(const UInt8 * map, UInt32 mappingLen, NXParsedKeyMapping * parsedMapping)
can be reached by a regular user by setting the HIDKeyMapping property of IOHIDKeyboard in the IOKit registry.

This function is responsible for parsing a binary keymapping file.

The function NextNum reads either 1 or two bytes from map depending on whether the first two bytes in the file are non-zero.

  IOHIKeyboardMapper::parseKeyMapping(const UInt8 * map, UInt32 mappingLen, NXParsedKeyMapping * parsedMapping)
    ...
    parsedMapping->numSeqs = NextNum(&nmd);         <-- (a)
    if (parsedMapping->numSeqs <= maxSeqNum)        <-- (b)
      return false;
      
    for(i = 0; i < parsedMapping->numSeqs; i++)
    {
      parsedMapping->seqDefs[i] = (unsigned char *)nmd.bp; <-- (c)
      for(j=0, l=NextNum(&nmd); j<l; j++)
      {
              NextNum(&nmd);
              NextNum(&nmd);
      }
    }

At (a) this function reads the number of sequence definitions follow, at (b) this number is checked against a lower bound however
there is no upper-bound check. By specifying a large value for numSeqs (can be up to 0xffff) we can overflow the fixed-size seqDefs buffer at (c).

seqDefs is defined here:
  
  typedef struct _NXParsedKeyMapping_ {
    short shorts;
    char  keyBits[NX_NUMKEYCODES];
    int     maxMod;
    unsigned char *modDefs[NX_NUMMODIFIERS];
    int     numDefs;
    unsigned char *keyDefs[NX_NUMKEYCODES];
    int     numSeqs;
    unsigned char *seqDefs[NX_NUMSEQUENCES];         <-- seqDefs ( #define NX_NUMSEQUENCES  128 )
    int     numSpecialKeys;
    unsigned short specialKeys[NX_NUMSPECIALKEYS];
    const unsigned char *mapping;
    int mappingLen;
  } NXParsedKeyMapping;

It's actually very easy to get code execution with this buffer overflow:

This NXParsedKeyMapping is a member var of IOHIKeyboardMapper:

class IOHIKeyboardMapper : public OSObject
{
private:
  IOHIKeyboard *    _delegate;
  bool        _mappingShouldBeFreed;
  NXParsedKeyMapping  _parsedMapping;                       <-- _parsedMapping.seqDefs will be overflowed
  IOHIDSystem  *    _hidSystem;
...
  ExpansionData * _reserved;
...
  UInt32              _stickyKeys_State; 
  int           _stickyKeys_NumModifiersDown;
  UInt8         _stickyKeys_Modifiers[kMAX_MODIFIERS];
  StickyKeys_ToggleInfo * _stickyKeys_ShiftToggle;
  StickyKeys_ToggleInfo * _stickyKeys_OptionToggle;
  bool        _stateDirty;
  OSDictionary *    _onParamDict;                           <-- pointer to an object with virtual method

We can easily cause a virtual method to be called on _onParamDict by ensuring that parseKeyMapping returns false indicating
an error parsing the keyfile. This will cause a call to: IOHIKeyboardMapper::free which will call stickyKeysFree:

   void IOHIKeyboardMapper::stickyKeysfree (void)
  {
    ...
    if (_onParamDict)            <-- we overwrite this pointer
      _onParamDict->release();   <-- virtual method ( offset 0x28 in the vtable)

Some offsets:

_onParamDict is at offset 0xe30 in the IOHIDKeyboardMapper

seqDefs is at offset 0x998 in NXParsedKeyMapping, and NXParsedKeyMapping is at offset 0x20 in IOHIKeyboardMapper:

+0x000: IOHIKeyboardMapper
...
+0x020:   NXParsedKeyMapping _parsedMapping
...
+0x9b8:     unsigned char* seqDefs[]  -+
...                                    | 0x478 bytes -> 0x8f pointers
+0xe30:   OSDictionary* _onParamDict  -+


So the 0x90th seqDef entry will overlap with the _onParamDict pointer.

Looking again at the parseKeyMapping function snippet where the overflow takes place:

  for(i = 0; i < parsedMapping->numSeqs; i++)
  {
    parsedMapping->seqDefs[i] = (unsigned char *)nmd.bp;
    for(j=0, l=NextNum(&nmd); j<l; j++)
    {
            NextNum(&nmd);
            NextNum(&nmd);
    }

(nmb.bp points to the next number to parse.)

This loop is parsing a length followed by twice that number of Nums.

We can supply 0x8f entries of: 0x00 0x00 (len=0)
followed by one entry of: 0x00 0x02 (len = 0x2)
                          0x00 0x78 0x56 0x00
                          0x00 0x00 0x00 0x00

which will overwrite the _onParamDict pointer with a pointer to: 0x5678000000 which will be intepreted as a pointer to
a vtable :-) We can then map that page in userspace and write a fake vtable at 0x5678000200.

We can then force the parseKeyMapping function to return false here by supplying a value larger than NX_NUMSPECIALKEYS:
  numMods = NextNum(&nmd);
  parsedMapping->numSpecialKeys = numMods;
  if ( numMods > NX_NUMSPECIALKEYS )
    return false;

PoC attached.
 
key_mapping_exploit.c
6.4 KB Download
Project Member Comment 1 by ianbeer@google.com, Jun 30 2014
Labels: Reported-2014-June-30 Id-607551907
Project Member Comment 2 by ianbeer@google.com, Aug 22 2014
Labels: Deadline-90
Comment 3 by cevans@google.com, Sep 23 2014
Labels: -Reported-2014-June-30 Reported-2014-Jun-30 CVE-2014-4404
http://support.apple.com/kb/HT6441 (i.e. also affect iOS)
No mention of this CVE in http://support.apple.com/kb/HT6443 ??
Comment 4 by cevans@google.com, Sep 23 2014
Labels: Product-iOS-Kernel
Project Member Comment 5 by ianbeer@google.com, Sep 24 2014
Labels: -Restrict-View-Commit
Comment 6 by cevans@google.com, Oct 17 2014
Labels: Deadline-Exceeded Fixed-2014-Oct-17
Status: Fixed
Interesting case.
Looks like it wasn't fixed in OS X until Yosemite: 
https://support.apple.com/kb/HT6535. Therefore, it can be observed:

1) By declaring this in the earlier iOS patch, Apple dropped on bug on their own OS X software.

2) The original report was against OS X, not iOS, so this definitely went over deadline -- by a month(!) Marking as such.
Comment 7 by cevans@google.com, Oct 17 2014
Correction, this one didn't quite get near a month past deadline, but still significantly so.
Sign in to add a comment