技术控

    今日:118| 主题:49179
收藏本版 (1)
最新软件应用技术尽在掌握

[其他] BitUnmap: Attacking Android Ashmem

[复制链接]
嫉妒恨 发表于 前天 07:05
76 6

立即注册CoLaBug.com会员,免费获得投稿人的专业资料,享用更多功能,玩转个人品牌!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
The  law of leaky abstractions  states that “all non-trivial abstractions, to some degree, are leaky”. In this blog post we’ll explore the ashmem shared memory interface provided by Android and see how false assumptions about its internal operation can result in security vulnerabilities affecting core system code.
   We’ll walk through the process of discovering and exploiting a vulnerability resulting from this leaky abstraction, which will allow us to elevate our privileges from any Android application to a multitude of privileged contexts, including the highly-privileged “system_server”. This vulnerability has been present in the core Android platform code for the Marshmallow and Nougat versions. It has now been fixed in the  recent Android bulletin  . For a detailed disclosure timeline, see the “Timeline” section below.
   One Device to Bind Them

   As you know, Android applications can perform inter-process communication (for example, in order to communicate with various Android services), by using the Android  binder  . Initially, each Android service registers itself with a central daemon; the “ service manager ”. Subsequently, applications may contact the daemon in order to request “handles” with which the registered services may be contacted.
   In keeping with good IPC design, the interface provided by binder itself is rather simplified. In fact, binder transactions are capped to a size of  at-most 1MB  . Well then, what about scenarios in which we need to transfer a large amount of data to a system service? For example, what if we want to modify the phone’s wallpaper? What about playing a media file? Surely we wouldn’t need to “re-invent” a mechanism through which memory can be shared in larger quantities between processes using binder transactions.
   Indeed, there’s no need to worry. You see, binder transactions support the transfer of more than just binary data. In fact, binder transactions can be used to transfer file descriptors and even other binder handles. For instance, this is how the “service manager” is able to provide processes with handles through which they may communicate with the requested services.
   
BitUnmap: Attacking Android Ashmem-1 (Android,privileged,multitude,operation,interface)
  
   Great, so we have a means of communicating with other processes - even in large volumes. But what about cases where we’d like to share (rather than simply transfer ) large quantities of memory? Once again - no need to worry! For this purpose, Android has introduced a means of sharing memory, called “ashmem” ( A ndroid Sh ared Mem ory).
   Sharing (memory) is Caring

   So what exactly is ashmem ? In short, each ashmem file descriptor acts as a handle to a shared memory region . The device also allows the user to perform several memory-sharing operations via a set of supported ioctl s. These allow the user to set the size of the shared memory region referred to by the ashmem file descriptor, modify the name of the shared region and even control the protection mask with which this descriptor may be mmap -ed.
   Let’s take a closer look at the actual implementation of the ashmem device, starting with the ioctl used to control the size of a shared memory region -  ASHMEM_SET_SIZE  :
      static long  ashmem_ioctl  ( struct  file  *  file  , unsigned int  cmd  , unsigned long  arg  )  
  {  
      struct  ashmem_area  *  asma  =  file  ->  private_data  ;  
      long  ret  = -  ENOTTY  ;
     switch (  cmd  ) {  
          …

BitUnmap: Attacking Android Ashmem-2 (Android,privileged,multitude,operation,interface)

          ...  
    }  
      return  ret  ;  
  }
      
  As we can see above, this ioctl simply allows the user to pass in any size. As long as the shared memory region has not been mapped yet, the device will happily record the passed-in size as the underlying size corresponding to the memory region. Not only that, but the recorded size can then be queried by the user by issuing the corresponding ioctl -  ASHMEM_GET_SIZE  .
   So… this looks rather suspicious. What exactly does “setting the size” actually mean? Recall that ashmem regions are mapped-in by calling mmap , and, as we know, the mmap syscall receives an argument denoting the size of the mapping to be created.
   This begs the question: what if the size of a created mapping does not match the size of an ashmem region? Perhaps the implementation of mmap will simply ignore the passed in size argument and use the size provided in ASHMEM_SET_SIZE ? Perhaps, instead, the implementation of mmap will simply ignore the underlying size and use the size argument instead? There’s only one way to  find out  :
      static int  ashmem_mmap  ( struct  file  *  file  , struct  vm_area_struct  *  vma  )  
  {  
      struct  ashmem_area  *  asma  =  file  ->  private_data  ;  
      ...  
      if (!  asma  ->  file  ) {  
         ...
      vmfile  =  shmem_file_setup  (  name  ,  asma  ->  size  ,  vma  ->  vm_flags  );  
         ...
      asma  ->  file  =  vmfile  ;  
     }  
     ...
      vma  ->  vm_file  =  asma  ->  file  ;
      mutex_unlock  (&  ashmem_mutex  );  
      return  ret  ;  
  }
     As we can see above, while the actual shared memory file is created using the size of the shared memory region (using “ shmem_file_setup ”), the memory mapping itself is created using the size in the virtual memory area ( vma) - that is, the size passed in to mmap .
   Let’s take a step back and reflect on this decision.
   Essentially, this means that developers who attempt to use an ashmem region must take special care to always call mmap using the same size of the underlying shared memory region. Failing to do so will not return any visible error code to the developer, but will instead create a mapping with potentially dangerous consequences.
   For starters, if a developer mmap- s a region with a size larger than the actual underlying region’s size, any attempt to access memory beyond the bounds of the underlying region will result in a illegal access, sending the SIGBUS signal to the process (which will then probably subsequently terminate).
   But there’s another, more interesting, potential pitfall. What if the developer erroneously assumes that size of the underlying shared memory region and the mmap -ed size must be equal to one another? To try and answer this question, I’ve audited all instances of Android services which use ashmem to share memory with a user. From here on, we’ll focus on one such case - Bitmaps.
   Mismatching Assumptions

   As we mentioned earlier, one operation which requires the transfer of potentially large quantities of memory from one process to another is the exchange of images. For this purpose, Android exposes the  Bitmap  class, which can be easily serialized into a binder parcel.
   As Bitmaps may be larger than the 1MB limit for binder transactions, the image’s data must be transferred via other means - such as ashmem . Indeed, for bitmaps above a certain size, this is exactly how this data is transferred. We can see that by looking at the “unflattening” function, “  Bitmap_createFromParcel  ”:
      static  jobject   Bitmap_createFromParcel  (  JNIEnv  *  env  ,  jobject  ,  jobject   parcel  ) {
      android  ::  Parcel  * p =  android  ::  parcelForJavaObject  (  env  ,  parcel  );  
      const  SkColorType   colorType  = (  SkColorType  )p->  readInt32  ();  
      const  SkAlphaType   alphaType  = (  SkAlphaType  )p->  readInt32  ();  
      const int  width  = p->  readInt32  ();  
      const int  height  = p->  readInt32  ();  
      const int  rowBytes  = p->  readInt32  ();  
     …  
       std  ::  unique_ptr  <  SkBitmap  >  bitmap  ( new  SkBitmap  );  
      if (!  bitmap  ->  setInfo  (  SkImageInfo  ::  Make  (  width  ,  height  ,  colorType  ,  alphaType  ),  rowBytes  ) ) {  
          return  NULL  ;  
     }
    …
      size_t   size  =  bitmap  ->  getSize  ();  
       android  ::  Parcel  ::  ReadableBlob   blob  ;  
       android  ::  status_t   status  = p->  readBlob  (  size  , &  blob  ) ;
    …
    }
     First, the image’s metadata is extracted from the parcel and is used to construct an SkBitmap instance to hold this information. Next, the function proceeds to reads the actual bitmap’s data by calling Parcel::readBlob .
   However, this looks potentially dangerous! First of all, note that the function uses the SkBitmap instance (i.e., the one holding all the metadata we read earlier on) in order to calculate the “size” of the resulting bitmap’s data. Then, it calls Parcel::readBlob in order to actually read the transferred data, while using the previously calculated size as an input argument.
   Finally, let’s take a look at how Parcel::readBlob actually reads the enclosed Bitmap’s data:
       status_t   Parcel  ::  readBlob  (  size_t   len  ,  ReadableBlob  *  outBlob  ) const  
  {  
       int32_t   blobType  ;  
       status_t   status  =  readInt32  (&  blobType  );
    …  
      int  fd  =  readFileDescriptor  ();  
     ...
     void *  ptr  = ::  mmap  (  NULL  , l  en  ,
      isMutable  ?  PROT_READ  |  PROT_WRITE  :  PROT_READ  ,  
                                       MAP_SHARED  , f  d  , 0);
    ...  
      return  NO_ERROR  ;
    }
     Aha! This is exactly what we were looking for. Instead of using the underlying size of the memory region, Parcel::readBlob performs an mmap operation using our own controlled length argument (i.e., the size computed from the bitmap’s metadata).
   In and of itself, this is already a bad practice; the user could pass in an ashmem descriptor with any arbitrary size, resulting in an “invalid” memory mapping of the type we discussed earlier. This, in turn, would trigger a SIGBUS if the receiving application attempts to access the mmap -ed memory region.
   But perhaps we can do better than just creating an invalid mapping? We’ve seen how the memory is mapped-in when creating a Bitmap, but how is it unmapped?
   Well, after reading in the bitmap’s data, Bitmap_createFromParcel proceeds to store the information about the mapped data in the constructed Bitmap instance:
       Bitmap  ::  Bitmap  ( void *  address  , int  fd  ,  
                              const  SkImageInfo  &  info  ,  size_t   rowBytes  ,  SkColorTable  *  ctable  )  
                                 :  mPixelStorageType  (  PixelStorageType  ::  Ashmem  ) {  
       mPixelStorage  .  ashmem  .  address  =  address  ;  
       mPixelStorage  .  ashmem  .  fd  =  fd  ;  
       mPixelStorage  .  ashmem  .  size  =  ashmem_get_size_region  (  fd  );  
       mPixelRef  .  reset  ( new  WrappedPixelRef  ( this ,  address  ,  info  ,  rowBytes  ,  ctable  ));  
      // Note: this will trigger a call to onStrongRefDestroyed(), but  
      // we want the pixel ref to have a ref count of 0 at this point  
       mPixelRef  ->  unref  ();  
  }
      
  However, instead of using the size of the previously created mapping, the constructor simply retrieves the size of the mapping using the ASHMEM_GET_SIZE ioctl (using the thin-wrapper ashmem_get_size_region ). This is, once more, a false assumption - namely, that the underlying ashmem size and the size of the mmap -ed region must be equal to one another. Finally, once the bitmap is freed, the area is unmapped by calling:
      void  Bitmap  ::  doFreePixels  () {  
      switch (  mPixelStorageType  ) {
    ...  
          case  PixelStorageType  ::  Ashmem  :  
               munmap  (  mPixelStorage  .  ashmem  .  address  ,  mPixelStorage  .  ashmem  .  size  );  
               close  (  mPixelStorage  .  ashmem  .  fd  );  
              break ;  
         ...
    }
    }
      
  Putting it all together, this means that the mmap and the munmap calls are performed with potentially different length arguments, both of which are fully controllable by the attacker:
  
       
  •   The length of the mmap operation is calculated from the bitmap’s metadata
       
  •   The length of the munmap operation is calculated from the ashmem ’s size
      
   The mismatch between the mmap -ed and munmap -ed length provides us with a great exploitation primitive! Specifically, we could supply a short length for the mmap operation and a longer length for the munmap operation - thus resulting in deletion of an arbitrarily large range of virtual memory following our bitmap object. Moreover, there’s no need for the deleted range to contain one continuous memory mapping, since the range supplied in munmap  simply ignores unmapped pages  .
   Once we delete a range of memory, we can then attempt to “re-capture” that memory region with controlled data, by causing another allocation in the remote process. By doing so, we can forcibly “free” a data structure and replace its contents with our own chosen data -- effectively forcing a use-after-free condition.
   From here on, we’ll refer to these crafted bitmaps with mismatching lengths as “ BitUnmap ”s.
友荐云推荐




上一篇:在大学中教授现代软件开发技能
下一篇:字节码操纵技术探秘
酷辣虫提示酷辣虫禁止发表任何与中华人民共和国法律有抵触的内容!所有内容由用户发布,并不代表酷辣虫的观点,酷辣虫无法对用户发布内容真实性提供任何的保证,请自行验证并承担风险与后果。如您有版权、违规等问题,请通过"联系我们"或"违规举报"告知我们处理。

时间都去哪了 发表于 前天 08:47
想念你,真的不需要理由,那只是一种感觉,随时乘我不备,就会窜入我的脑海,直达我的心脏。
回复 支持 反对

使用道具 举报

刘刚 发表于 前天 10:29
你觉得该怎么做呢?
回复 支持 反对

使用道具 举报

lkbhjh..; 发表于 前天 12:08
你拥有再大再多的水桶,也不如有一个水龙头。说明:”渠道很重要!
回复 支持 反对

使用道具 举报

紫安 发表于 前天 21:47
又见技术帖
回复 支持 反对

使用道具 举报

cuy123 发表于 前天 22:02
不错,顶一个!
回复 支持 反对

使用道具 举报

sgpel 发表于 昨天 11:32
星期六的心情很不错啊
回复 支持 反对

使用道具 举报

*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

我要投稿

推荐阅读

扫码访问 @iTTTTT瑞翔 的微博
回页顶回复上一篇下一篇回列表手机版
手机版/CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 )|网站地图 酷辣虫

© 2001-2016 Comsenz Inc. Design: Dean. DiscuzFans.

返回顶部 返回列表