Android Minikin 库越界写拒绝服务漏洞分析

2016-04-28 323409人围观 ,发现 2 个不明物体 系统安全

在本月的Android补丁中,谷歌修复了Minikin库中的一个拒绝服务漏洞(CVE-2016-2414) 。早在今年3月初我就向谷歌报告了这个漏洞,但谷歌方面确认说该漏洞与去年11月份另一位专家报告的BUG26413177是同一个漏洞。

在本文,我将给大家深入的分析该漏洞。该漏洞是由于Minikin库无法正确解析.TTF字体文件,正因如此一位本地攻击者是能够暂时阻止受该漏洞影响的Android设备访问。攻击者可以加载一份不可信字体文件,让Minikin组件溢出,这将会导致崩溃。

由于崩溃会导致Android设备连续的重启,所以该问题被谷歌评级为高危漏洞。

受影响的Android版本

Android 5.0.2, 5.1.1, 6.0, 6.0.1

概念验证

下面的代码片段能够触发该漏洞,setTypeface函数用于从外部在TextView中加载以及设置自定义字体。

public class MyActivity extends Activity
    {
        public void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
            TextView textView2 = (TextView) findViewById(R.id.textView2);
            Typeface typeface2 = Typeface.createFromFile(Environment.getExternalStorageDirectory() + "/fuzzed.ttf");
            textView2.setTypeface(typeface2);
        }
    }

以下为崩溃日志:

--------- beginning of crash
04-04 16:44:43.230  5021  5021 F libc    : Fatal signal 11 (SIGSEGV), code 1, fault addr 0x36587 in tid 5021 (example.TTFFont)
04-04 16:44:43.248 32383 28153 I Icing   : Indexing done 215BF78BC46028A346E51FB3E9502079034D8D40
04-04 16:44:43.249 32383 28153 I Icing   : Indexing 4400EDF81586FA899DD2C6CB98D8E1B8279596F4 from com.google.android.gms
04-04 16:44:43.250 32383 28153 I Icing   : Indexing done 4400EDF81586FA899DD2C6CB98D8E1B8279596F4
04-04 16:44:43.332 10881 10881 F DEBUG   : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 
04-04 16:44:43.332 10881 10881 F DEBUG   : Build fingerprint: 
'google/hammerhead/hammerhead:6.0.1/MMB29V/2554798:user/release-keys'
04-04 16:44:43.332 10881 10881 F DEBUG   : Revision: '0' 04-04 16:44:43.332 10881 10881 F DEBUG   : ABI: 'arm'
04-04 16:44:43.332 10881 10881 F DEBUG   : pid: 5021, tid: 5021, name: 
example.TTFFont  >>> com.example.TTFFont <<<
04-04 16:44:43.332 10881 10881 F DEBUG   : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x36587
04-04 16:44:43.344 10881 10881 F DEBUG   :     r0 0000d962  r1 0000d938  r2 0000001e  r3 00000006
04-04 16:44:43.344 10881 10881 F DEBUG   :     r4 acb4ef7c  r5 0000001e  r6 ffffffff  r7 00000043
04-04 16:44:43.344 10881 10881 F DEBUG   :     r8 ab173400  r9 ffffffff  sl 0000002b  fp 07fffffe
04-04 16:44:43.345 10881 10881 F DEBUG   :     ip 07fffffd  sp bef6ce20  lr 00001e0c  pc b5db01e0  cpsr 00030030
04-04 16:44:43.347 10881 10881 F DEBUG   : 
04-04 16:44:43.347 10881 10881 F DEBUG   : backtrace:
04-04 16:44:43.347 10881 10881 F DEBUG   :     #00 pc 0000b1e0 
 /system/lib/libminikin.so 
(android::SparseBitSet::initFromRanges(unsigned int const*, unsigned 
int)+311)
04-04 16:44:43.347 10881 10881 F DEBUG   :     #01 pc 0000652b 
 /system/lib/libminikin.so 
(android::CmapCoverage::getCoverage(android::SparseBitSet&, unsigned
 char const*, unsigned int)+498)
04-04 16:44:43.347 10881 10881 F DEBUG   :     #02 pc 00006ee1 
 /system/lib/libminikin.so (android::FontFamily::getCoverage()+116)
04-04 16:44:43.347 10881 10881 F DEBUG   :     #03 pc 0000685b 
 /system/lib/libminikin.so 
(android::FontCollection::FontCollection(std::__1::vector<android::FontFamily*,
 std::__1::allocator<android::FontFamily*> > const&)+150)
04-04 16:44:43.347 10881 10881 F DEBUG   :     #04 pc 0009784b 
 /system/lib/libandroid_runtime.so 
(android::TypefaceImpl_createFromFamilies(long long const*, unsigned 
int)+94)
04-04 16:44:43.347 10881 10881 F DEBUG   :     #05 pc 0009752b  /system/lib/libandroid_runtime.so
04-04 16:44:43.347 10881 10881 F DEBUG   :     #06 pc 72e8db21 
 /data/dalvik-cache/arm/system@framework@boot.oat (offset 0x1ec9000)
04-04 16:44:43.649 10881 10881 F DEBUG   : 
04-04 16:44:43.649 10881 10881 F DEBUG   : Tombstone written to: /data/tombstones/tombstone_09
04-04 16:44:43.649 10881 10881 E DEBUG   : AM write failed: Broken pipe 

分析

该漏洞是由于Minikin库无法正确解析.TTF字体文件,进而造成了一个越界写拒绝服务漏洞。

首先我们来看看这个经过特殊构造的.TTF字体文件,注意这个简化版本的PoC在偏移地址0×60, 0x58D, 0x5D6, 0xAD60的不同差异。以下为正常的TTF文件与我们的简化版本PoC比较

CVE-2016-2414 1.png

图1:偏移地址0×60

CVE-2016-2414 2.png

图2:偏移地址0x58D以及0x5D6

CVE-2016-2414 3.png

图3:偏移地址0xAD60

在上面的图1和图3中,我们对TTFTemplate使用16进制编辑器来解析TTF字体文件。偏移地址0×60
的4个字节为cmap表单中checksum字段,偏移地址0xAD60的4个字节为thead头结构中的checkSumAdjustment字段,这两个校验和字段在fuzzing的时候就已经进行重新计算了。他们不触发漏洞,这里就直接略过。以下为使用TTFTemplate解析简化版本PoC

CVE-2016-2414 4.png

从上图中,我们能够看到偏移地址0x58D和0x5D6中cmap表中被修改的字节。关于cmap表的详细说明可以看看https://www.microsoft.com/typography/otspec/cmap.htm,其中的Format 4部分如下:

CVE-2016-2414 5.png

以下为‘startCount[]’ 和 ‘endCount[]’的对比:

CVE-2016-2414 6.png

一般来说,startCount[]和endCount[]数组中每一个元素的值都会逐渐增大。与此同时,对于一个给定的索引i,endCount[i]大于或等于startCount[i]。我们将简化版PoC文件中的startCount[30]修改为0x1E78,让startCount[30] > endCount[30]。再将 startCount[67] 修改为0xE0FF,让startCount[67] < startCount[66]。根据说明文档我们知道startCount[67]是最后一个元素且为0xFFFF。

从堆栈跟踪反馈来看,我们看到在android::SparseBitSet::initFromRanges函数中SIGSEGV重现并获得代码崩溃的位置,以下为SparseBitSet::initFromRanges函数:

  void SparseBitSet::initFromRanges(const uint32_t* ranges, size_t nRanges) {
       ...
       ...
       ...
            size_t index = ((currentPage - 1) << (kLogValuesPerPage - kLogBitsPerEl)) +
                ((start & kPageMask) >> kLogBitsPerEl);
            size_t nElements = (end - (start & ~kElMask) + kElMask) >> kLogBitsPerEl;
            if (nElements == 1) {
               mBitmaps[index] |= (kElAllOnes >> (start & kElMask)) &
                    (kElAllOnes << ((-end) & kElMask));
            } else {
                mBitmaps[index] |= kElAllOnes >> (start & kElMask);
                for (size_t j = 1; j < nElements - 1; j++) {
                    mBitmaps[index + j] = kElAllOnes;                       //crash here, it’s an out-of-bound write.
                }
               mBitmaps[index + nElements - 1] |= kElAllOnes << ((-end) & kElMask);    
           }
        ...
        ...
        ...
    }

接下来,使用 IDA Pro进行动态分析。在SparseBitSet::initFromRanges函数入口设置一个断点。然后运行调试器可以看到下面的代码:

CVE-2016-2414 7.png

从上图中得知,R1为第一个参数(const uint32_t* ranges),R2为第二个参数 (size_t nRanges),下面为R1指向的内存:

CVE-2016-2414 8.png

我们看到偏移地址0xAB1734F0中的4个字节 |78 1E 00 00|存储 startCount[30] (|1E 78|)的值,随后的4个字节|0C 1E 00 00|存储endCount[30]+1 (|1E 0B|+1)的值,接着继续跟踪直到程序处理到ranges[30] (|78 1E 00 00 0C 1E 00 00|)

CVE-2016-2414 9.png

从上图中我们可以看到当R2等于0x1E,R10寄存器指向ranges[30] (0xAB1734F0),继续分析

CVE-2016-2414 10.png

之后跳转到loc_B5DB0176

CVE-2016-2414 11.png

接下来进入loc_B5DB01D2循环, [R4+8]存储mBitmaps值,并且R9指向值mBitmaps(从源代码)。如下所示:

CVE-2016-2414 12.png

之后在地址B5DB01E0,它会执行一个STR指令并向内存 [R9+R0<<2]写入0xFFFFFFFF

CVE-2016-2414 13.png

当我们运行调试器(F9),弹出如下对话框

CVE-2016-2414 14.png

出现一个分段违规(segmentation violation)错误,检查下内存信息和寄存器

CVE-2016-2414 15.png

以下为源代码的分析:

 void SparseBitSet::initFromRanges(const uint32_t* ranges, size_t nRanges) {
       if (nRanges == 0) {
            mMaxVal = 0;
            mIndices.reset();
            mBitmaps.reset();
            return;
        }
        mMaxVal = ranges[nRanges * 2 - 1];
        size_t indexSize = (mMaxVal + kPageMask) >> kLogValuesPerPage;
        mIndices.reset(new uint32_t[indexSize]);
        uint32_t nPages = calcNumPages(ranges, nRanges);
        mBitmaps.reset(new element[nPages << (kLogValuesPerPage - kLogBitsPerEl)]);
        memset(mBitmaps.get(), 0, nPages << (kLogValuesPerPage - 3));
        mZeroPageIndex = noZeroPage;
        uint32_t nonzeroPageEnd = 0;
        uint32_t currentPage = 0;
        for (size_t i = 0; i < nRanges; i++) {      //when the variable i is eqaul to 0x1E, the program handles the modified data in 'startCount' array in PoC file.
            uint32_t start = ranges[i * 2];         //the variable start is eqaul to 0x1E78
            uint32_t end = ranges[i * 2 + 1];       //the variable end is eqaul to 0x1E0C
            uint32_t startPage = start >> kLogValuesPerPage;
            uint32_t endPage = (end - 1) >> kLogValuesPerPage;
           if (startPage >= nonzeroPageEnd) {
                if (startPage > nonzeroPageEnd) {
                   if (mZeroPageIndex == noZeroPage) {
                        mZeroPageIndex = (currentPage++) << (kLogValuesPerPage - kLogBitsPerEl);
                    }
                    for (uint32_t j = nonzeroPageEnd; j < startPage; j++) {
                        mIndices[j] = mZeroPageIndex;
                    }
                }
                mIndices[startPage] = (currentPage++) << (kLogValuesPerPage - kLogBitsPerEl);
            }
     
            size_t index = ((currentPage - 1) << (kLogValuesPerPage - kLogBitsPerEl)) +
                ((start & kPageMask) >> kLogBitsPerEl);                          // the variable index is equal to 0x2B from dynamic debugging.
            size_t nElements = (end - (start & ~kElMask) + kElMask) >> kLogBitsPerEl;  // nElements = (0x1E0C - (0x1E78 & ~0x1F) + 0x1F) >> 5 = 0x07FFFFFE, because start is greater than end, it causes a overflow when doing subtraction.
            if (nElements == 1) {
               mBitmaps[index] |= (kElAllOnes >> (start & kElMask)) &
                    (kElAllOnes << ((-end) & kElMask));
            } else {
                mBitmaps[index] |= kElAllOnes >> (start & kElMask);
                for (size_t j = 1; j < nElements - 1; j++) {        // the loop condition is j < 0x07FFFFFD, it's a too large value.
                    mBitmaps[index + j] = kElAllOnes;               //crash here, it's out-of-bound write.
                }
               mBitmaps[index + nElements - 1] |= kElAllOnes << ((-end) & kElMask);
           }
            for (size_t j = startPage + 1; j < endPage + 1; j++) {
                mIndices[j] = (currentPage++) << (kLogValuesPerPage - kLogBitsPerEl);
            }
            nonzeroPageEnd = endPage + 1;
        }
    }

总结

简单来说该漏洞是由于Minikin未能检测cmap表中的有效长度引起,一个损坏的或者一个恶意的字体文件可能包含一个负数范围大小,反过来导致了内存的泄漏。一个攻击者可以加载一个损坏的字体文件并导致Minikin组件发生溢出。这个崩溃会导致Android设备连续重启。

*原文链接:Fortinet,鸢尾编译,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)

相关推荐
Loading...

特别推荐

推荐关注

官方公众号

聚焦企业安全

填写个人信息

姓名
电话
邮箱
公司
行业
职位
css.php