首页
社区
课程
招聘
[翻译]Firefox中一个Cross-mmap溢出的利用(CVE-2016-9066)
2017-3-23 15:32 3725

[翻译]Firefox中一个Cross-mmap溢出的利用(CVE-2016-9066)

2017-3-23 15:32
3725

Firefox中一个Cross-mmap溢出的利用

2017-3-10


这篇文章将会探索一下CVE-2016-9066,这是Firefox中一个简单却有趣(从实际操作的角度来看)的漏洞,可以利用该漏洞来获取代码执行权限。

一段负责加载脚本标签的代码中的一个整数溢出导致了对mmap结束块的越界写操作。一种利用方法是在缓冲区后放置一个JavaScript堆以便便溢出到它的元数据中来创建一个假的未使用堆单元。然后可以将一个ArrayBuffer实例放置在另一个ArrayBuffer的内联数据中。ArrayBuffer对象内部可以被任意修改,就产生了任意读/写原语。至此,实现代码执行就变的非常简单了。完整的exploit可以在此处找到,并针对macOS 10.11.6平台上的Firefox 48.0.1进行了测试。bugzilla报告可在此处找到。

漏洞

以下代码用来加载外部脚本标签:

result
nsScriptLoadHandler::TryDecodeRawData(const uint8_t* aData,
                                      uint32_t aDataLength,
                                      bool aEndOfStream)
{
  int32_t srcLen = aDataLength;
  const char* src = reinterpret_cast<const char *>(aData);
  int32_t dstLen;
  nsresult rv =
    mDecoder->GetMaxLength(src, srcLen, &dstLen);

  NS_ENSURE_SUCCESS(rv, rv);

  uint32_t haveRead = mBuffer.length();
  uint32_t capacity = haveRead + dstLen;
  if (!mBuffer.reserve(capacity)) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  rv = mDecoder->Convert(src,
                         &srcLen,
                         mBuffer.begin() + haveRead,
                         &dstLen);

  NS_ENSURE_SUCCESS(rv, rv);

  haveRead += dstLen;
  MOZ_ASSERT(haveRead <= capacity, "mDecoder produced more data than expected");
  MOZ_ALWAYS_TRUE(mBuffer.resizeUninitialized(haveRead));

  return NS_OK;
}

当服务器中的新数据到达时这段代码将被OnIncrementalData调用。这是一个简单的整数溢出Bug,当服务器发送超过4GB数据的时候即可发生。数据超过4GB的情况下,capacity将会环回(wrap around),接下来的对mBuffer.reserve函数的调用并不会修改缓冲区。接下来mDecode->Convert函数将会把数据写入到8GB缓冲区的尾部(数据在浏览器中以char16_t的形式存储),这部分内存将会通过mmap块备份(对于一个非常大的块这是通用做法)。

修补也是相当简单:

int32_t haveRead = mBuffer.length();
-  uint32_t capacity = haveRead + dstLen;
-  if (!mBuffer.reserve(capacity)) {
+
+  CheckedInt<uint32_t> capacity = haveRead;
+  capacity += dstLen;
+
+  if (!capacity.isValid() || !mBuffer.reserve(capacity.value())) {
     return NS_ERROR_OUT_OF_MEMORY;
   }

这个漏洞第一眼看上不并没有什么搞头。它有一个必要条件,需要发送和申请多大几个GB的数据。正如我们即将看到的,该漏洞在我的2015 MacBook Pro上相当可靠的被利用,完成整个利用代码只需打开页面用时不到1分钟。我们接下来先来探索一下此漏洞为什么会在macOS上被利用并弹出一个计算器,然后我们来改进一下利用代码让它变的更可靠一些,并且占用更低的带宽(剧透:我们将会使用HTTP压缩)

操作

当超过mmap区的溢出发生时,我们首先关注的是有没有可能在溢出的内存之后可靠地申请一块空间。与一些堆分配器相反,mmap(可以看作是内核提供的内存分配器)是非常具有确定性的:如果没有合适的内存块,调用mmap两次将会导致两次连续的内存映射。你可以用下边的代码来尝试一下。注意,实验的结果的异同取决于代码是运行在Linux系统还是macOS系统上。mmap内存区相较于macOS系统上由低向高增长,在Linux系统上,是由高向低增长。在本篇文章接下来的部分,我们将会专注于macOS。Linux或者Windows上应该也可能存在相似的利用代码。

#include <sys/mman.h>
#include <stdio.h>

const size_t MAP_SIZE = 0x100000;       // 1 MB

int main()
{
    char* chunk1 = mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
    char* chunk2 = mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);

    printf("chunk1: %p - %p\n", chunk1, chunk1 + MAP_SIZE);
    printf("chunk2: %p - %p\n", chunk2, chunk2 + MAP_SIZE);

    return 0;
}

上边的程序向我们展示了通过简单的映射所有的内存页直到所有已经存在的分页被填满,然后通过mmap再申请一块内存块。为了验证这个过程,我们接下来将会这样做:

  1. 加载一个包含一段脚本(将会触发溢出的payload.js)的HTML文档并且异步执行一些JavaScript代码(code.js,实现第3步和第5步)。
  2. 当浏览器请求payload.js时,使服务器返回一个Content-Length为0x100000001但是只发送0xffffffff字节的数据。
  3. 接下来,让JavaScript代码申请多个巨大(1GB)的ArrayBuffers(在缓冲区实际写入前,内存不一定被使用)。
  4. 让服务器发送payload.js剩下的2个字节。
  5. 检查所有ArrayBuffer对象的前几个字节,其中的某一个应该会包含服务器发送的数据。

为了实现这个过程,我们需要一些浏览器中的JavaScript代码和服务器之间的同步原语。为此,我在python的asyncio库智商写了一个小小的webserver,它包含一个方便的Event对象,用来和协同程序同步。创建两个全局事件可使客户端代码完成当前任务等待webserver进行下一步的操作时通知服务器。/sync的处理例程如下所示:

async def sync(request, response):
    script_ready_event.set()
    await server_done_event.wait()
    server_done_event.clear()

    response.send_header(200, {
        'Content-Type': 'text/plain; charset=utf-8',
        'Content-Length': '2'
    })

    response.write(b'OK')
    await response.drain()

客户端中我是用了同步XMLHttpRequests来阻塞脚本的执行,直到服务器完成相关工作:

function synchronize() {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', location.origin + '/sync', false);
    // Server will block until the event has been fired
    xhr.send();
}

这样,我们就可以实现上边的场景并且将会看到实际上有一个ArrayBuffer对象的开始处包含了我们的payload字节。不过还有一个小小的限制条件:我们只能通过有效的UTF-16来进行溢出,因为这是Firefox内部使用的。我们必须记住这一点。现在剩下的就是找到一些更有趣的事情,用内存分配来取代对ArrayBuffer的溢出。

寻找目标对象

因为malloc(同样的C++中的new操作)将在某些时候使用mmap请求更多的内存,所以像这些操作分配的内存可能是我们代码所感兴趣的。我走了一条不同的路线。最初我想检测一下是否有可能溢出到JavaScript对象中,比如说使数组的或者其他类似对象的长度腐败。为此,我开始围绕着JavaScript分配器深入发掘,来看JSObject被存储在哪里。Spidermonkey(Firefox中的JavaScript引擎)把JSObjet存储在两个独立的区域中:

  1. 永久堆(The tenured heap)。长生命周期的对象,同时少数被选择的对象类型在此处分配。这是一个相当经典的堆,跟踪自由内存然后在未来重复使用。
  2. 托管区。这是一块包含短暂生命周期对象的内存区域。大多数的JSObject最初都被分配在此处,然后如果在下一个垃圾回收周期( 包括更新所有指向他们的指针,因此要求牢记收集器知道其对象的所有指针)中依然存在则被移动到永久堆中。托管区不需要释放列表或者相似的结构:在一个垃圾回收循环之后,托管区只是在所有的存活对象被移出之后简单的声明一下自己是可用的。

对Spidermonkey内部更深入的讨论可以参见phrack上的这篇文章

永久堆中存储对象的容器叫做Arenas:

/*
 * Arenas are the allocation units of the tenured heap in the GC. An arena
 * is 4kiB in size and 4kiB-aligned. It starts with several header fields
 * followed by some bytes of padding. The remainder of the arena is filled
 * with GC things of a particular AllocKind. The padding ensures that the
 * GC thing array ends exactly at the end of the arena:
 *
 * <----------------------------------------------> = ArenaSize bytes
 * +---------------+---------+----+----+-----+----+
 * | header fields | padding | T0 | T1 | ... | Tn |
 * +---------------+---------+----+----+-----+----+
 * <-------------------------> = first thing offset
 */
class Arena
{
    static JS_FRIEND_DATA(const uint32_t) ThingSizes[];
    static JS_FRIEND_DATA(const uint32_t) FirstThingOffsets[];
    static JS_FRIEND_DATA(const uint32_t) ThingsPerArena[];

    /*
     * The first span of free things in the arena. Most of these spans are
     * stored as offsets in free regions of the data array, and most operations
     * on FreeSpans take an Arena pointer for safety. However, the FreeSpans
     * used for allocation are stored here, at the start of an Arena, and use
     * their own address to grab the next span within the same Arena.
     */
    FreeSpan firstFreeSpan;

    // ...

注释已经给出了非常好的总结:Arenas只是简单容器对象,其中分配了相同大小的JavaScript对象。它们位于容器对象内,这个块结构本身就是直接使用mmap来分配的。在Arena类中有趣的部分是它的firstFreeSpan成员:它是Arena对象(因此处于一个映射区的开始处)的第一个成员,并且本质上它指明了Arena中第一个未使用区块的索引。下边就是FreeSpan的大致结构:

class FreeSpan
{
    uint16_t first;
    uint16_t last;

    // methods following
}

其中的first和last在Arena中都是按字节索引,用来指明未使用区块链的头部。那么这就开辟了一条有趣的道路来利用这个漏洞:通过对Arena中firstFreeSpan成员对象的溢出,我们有可能在另一个对象中分配一个对象,最好是在某些可访问的内联数据中分配。那么接下来我们就能任意的修改内部分配的对象。

这个技巧有一下几点好处:

  • 我们将会看到能在Arena中使用指定的偏移分配一个JavaScript对象,这就产生了一条内存读写原语。
  • 我们只需溢出接下来的区块的4字节,因此不会破坏任何的指针或是其他的敏感数据。
  • Arenas/Chunks可以仅仅通过申请大量的JavaScript对象来可靠的产生。

事实证明,ArrayBuffer对象中高达96字节的数组会被内联存储在该对象头部之后后。这将会跳过托管过程并且因此它将会在Arena中被分配。这使得它们成为我们漏洞利用的理想选择。我们会这样做:

  1. 申请大量存储96字节的ArrayBuffer对象。
  2. 溢出,并且在我们的缓冲区之后,在Arena内部创建一个假的未使用内存块。
  3. 申请更多的同样大小的ArrayBuffer对象,看是否其中的某一个会被放置在另一个ArrayBuffer的数据中(通过扫描所有先前的ArrayBuffer中的不为空的内容即可)

垃圾回收的要求

不幸的是,这并不是那样简单:为了让Spidermonkey在我们的目标Arena(被破坏)中申请一个对象,那么这个Arena就必须在之前就被(部分)标记为可使用。这意味着,我们需要释放所有的Arena中至少一个存储块。我们可以通过删除每第25个ArrayBuffer(每个Arena有25个)来实现,然后强制进行垃圾回收。

Spidermonkey因为各种各样原因而触发垃圾回收。诸多方法中似乎使用TOO_MUCH_MALLOC来触发是最简单的一种:只要通过malloc分配了一定数量的字节,它就会被简单的触发。因此,下边的代码足以用来触发垃圾回收:

function gc() {
    const maxMallocBytes = 128 * MB;
    for (var i = 0; i < 3; i++) {
        var x = new ArrayBuffer(maxMallocBytes);
    }
}

在此之后,我们的目标Arena将会被放置在一个未使用标记链表中,随后的覆盖将会破坏该链表。下一次从被破坏的Arena中产生的分配将会返回一个假的处于一个ArrayBuffer对象的内联数据中内存块。

(可选阅读)压缩GC

实际上,这有点复杂了。存在一种叫做压缩GC模式的垃圾回收,这种模式将会把多个部分填充的Arena移动去填满另一个Arena。此举减少了内部碎片,并且协助释放整个内存区域以便系统回收。不管怎样,对于我们来讲,压缩GC着实是个麻烦,因为,它有可能填充了我们之前创建的目标Arena。以下的代码用来决定是否应该运行一个压缩GC:

bool
GCRuntime::shouldCompact()
{
    // Compact on shrinking GC if enabled, but skip compacting in incremental
    // GCs if we are currently animating.
    return invocationKind == GC_SHRINK && isCompactingGCEnabled() &&
        (!isIncremental || rt->lastAnimationTime + PRMJ_USEC_PER_SEC < PRMJ_Now());
}

查看一下代码,应该有方法来阻止压缩GC来运行(比如说,展示一些动画)。看来我们很幸运:上文中提到的gc函数(译注:用来产生ArrayBuffer的JS代码片段)将会在Spidermonkey中触发下边的代码流程,因此,阻止压缩GC的调用形式将会从GC_SHRINK变为GC_NORMAL

bool
GCRuntime::gcIfRequested()
{
    // This method returns whether a major GC was performed.

    if (minorGCRequested())
        minorGC(minorGCTriggerReason);

    if (majorGCRequested()) {
        if (!isIncrementalGCInProgress())
            startGC(GC_NORMAL, majorGCTriggerReason);       // <-- we trigger this code path
        else
            gcSlice(majorGCTriggerReason);
        return true;
    }

    return false;
}

编写Exploit

此刻,我们已经拼接了所有的碎片,可以实际动手写利用代码了。一旦我们创建了一个假的自由存储块并在其中创建一个ArrayBuffer对象,就能看见其中一个之前申请的ArrayBuffer中包含了我们的数据。ArrayBuffer对象的结构大致如下:

// From JSObject
GCPtrObjectGroup group_;

// From ShapedObject
GCPtrShape shape_;

// From NativeObject
HeapSlots* slots_;
HeapSlots* elements_;

// Slot offsets from ArrayBufferObject
static const uint8_t DATA_SLOT = 0;
static const uint8_t BYTE_LENGTH_SLOT = 1;
static const uint8_t FIRST_VIEW_SLOT = 2;
static const uint8_t FLAGS_SLOT = 3;

常量XXX_SLOT确定对象的起始位置相应值的偏移量。这样一来,数据指针(DATA_SLOT)将会被存储在addrof(ArrayBuffer) + sizeof(ArrayBuffer)

现在,我们就可以构建以下的代码利用原语:

  • 读取绝对内存地址:设置DATA_SLOT为所需的地址,然后从ArrayBuffer对象中读取。
  • 写入绝对内存地址:和上边一样,不过此刻变成了写操作。
  • 获取JavaScript对象的地址:为此,需要设置需要获取地址的对象为ArrayBuffer的内部属性,然后通过之前的读内存原语从slots_指针处读取地址。

进程持续

为了避免浏览器进程在下一次垃圾回收中崩溃,我们必须修复一下几点:

  • 在我们的利用代码中的ArrayBuffer之外的ArrayBuffer,将会被代码内的ArrayBuffer数据破坏。可以通过简单的拷贝另一个ArrayBuffer对象来覆盖被破坏的数据即可修复。
  • 我们原本在Arena中释放的区块此刻看起来应该是一个正在被使用的状态,同样回收器也是这样认为,这将会导致崩溃,因为这块数据已经被其他的数据(比如,FreeSpan实例)覆盖了。我们可以通过重新载入我们Arena中原始firstFreeSpan域的值来标记该块为可使用状态。

以上几点就足以保证浏览器在跑完利用代码之后仍然存活了。

概要

  1. 插入一个脚本标签加载payload,最终触发漏洞
  2. 等待服务器发送2GB+1字节的数据。浏览器此刻应该已经申请了我们最终要溢出的内存块。像我们最初的最简单的POC那样尝试使用ArrayBuffer对象来填充mmap中的区块。
  3. 申请包含大小为96字节(数据存储空间分配在对象之后的最大大小)的JavaScript Arenas(内存区域),接下来期待其中之一被放置在了我们将要溢出的缓冲区之后。由于mmap分配连续的区域,所以如果我们不申请足够的空间或者有什么其它的东西已经被分配在此处则可能会导致失败。
  4. 让服务器尽情的发送0xffffffff字节的数据,完全填充当前块。
  5. 释放所有的Arena中的一个ArrayBuffer,然后尝试触发垃圾回收,使Arena被插入到未使用块链表中。
  6. 使服务器发送剩下的数据。这将会触发溢出并破坏一个Arenas内部的一个未使用内存块链表。链表被改写使得其中之一的ArrayBuffer中第一个包含内联数据的自由块被包含在Arena中。
  7. 申请更多的ArrayBuffer。如果一切正常,其中之一的ArrayBuffer将会被分配在另一个ArrayBuffer内联数据内部。找到那个ArrayBuffer!。
  8. 如果找到了,构造一个任意内存读写原语。现在我们就可以修改ArrayBuffer内部的数据指针,所以这是非常容易做到的。
  9. 修复被破坏的对象,以便使我们的利用代码跑完之后浏览器进程依然存活。

弹出计算器

剩下的工作就是以某种方式弹出一个计算器。

一个简单的跑自己代码的方式是嘿咻JIT了,但是,这个技术(部分)在Firefox中被削弱了。考虑到我们开发的原语,可以绕过被削弱的部分(比如,使用ROP来转移控制),但对于简单的PoC来说,似乎有点复杂。

有一些其他的Firefox相关的技巧来通过滥用特权的JavaScript获取代码执行权限,但是这些需要对浏览器状态进行不必要的修改(比如, 关闭所有安全性,以便病毒可以接管这台电脑)。

我最终使用了一些标准的CTF技巧来完成利用代码:寻找对libc中第一个参数为字符串的函数的交叉引用(此例中,选用strcmp),我发现Date.toLocalFormat的执行并且注意到了该函数将第一个参数从JSString转换为C-String,它的第一个参数被用来做strcmp。因此,我们可以简单的strcmp的GOT换成system,然后执行data_obj.toLocaleFormat("open -a /Applications/Calculator.app");。搞定:).

改进Exploit

这时,基本的利用代码已经完成了, 接下来我们将会描述如何使它变的更可靠一些以及占用更少的带宽。

增强鲁棒性

此时此刻我们的利用代码知识申请了一些非常大的ArrayBuffer实例(每个1GB)用来填充mmap空间,然后再分配一大堆js :: Arena实例用来溢出。因此,该操作假设浏览器的堆操作在利用期间多多少少是确定的。既然这不一定是这样,我们希望让我们的漏洞更加强大一些。

快速浏览一下mozilla::Vector类(用来保留脚本缓冲区)接下来的操作向我们展示了它使用了realloc在其需要的时候来倍增自己的空间。由于jemalloc直接使用mmap来申请较大的区块,这就给了我们以下的分配模式:

  • mmap 1MB
  • mmap 2MB, munmap previous chunk
  • mmap 4MB, munmap previous chunk
  • mmap 8MB, munmap previous chunk
  • mmap 8GB, munmap previous chunk

因为当前区块的大小总是会大于之前所有区块的大小,这将导致我们的最终缓冲区之前存在大量的可用空间。理论上,我们可以计算空闲空间之和,然后申请一个大的ArrayBuffer。实际上,这行不通,因为当服务器发送数据到来时,在浏览器完成解压缩最后一块数据完成之前有一些其他的申请空间的操作。此外jemalloc还保留了一部分被释放的内存以供以后使用。相反,我们会尽快在浏览器中申请被释放的空间,理由如下:

  1. JavaScript代码使用sync来等待服务器
  2. 服务器发送到下一个 2 的幂(MB)的所有数据从而在最后触发一次对realloc的调用。浏览器此时将会释放一个大小已知的空间。
  3. 服务器设置server_done_event信号,使JavaScript代码继续执行。
  4. JavaScript代码申请一个与之前空间大小相同的ArrayBuffer实例来填充该空间。
  5. 重复上述步骤直到我们发送完0x80000001字节数据(强制申请最后一块空间)。

这个简单的算法服务器端代码在这里客户端第一步的代码在这里。使用这个算法,我们可以通过仅喷射几兆的ArrayBuffer实例而不是多个千兆字节来相当可靠地获得分配在目标缓冲区之后的空间。

减少网络负载

目前,我们的利用代码需要通过服务器发送4GB的数据。可以简单的通过下述方法来改进:使用HTTP压缩。zlib有一个好处,支持流式压缩,它可以逐步压缩payload。有了这个,我们只需将payload的每个部分添加到在zlib流中,然后调用flush来获取payload的下一个压缩块,并将其发送到服务器(译注:服务器发送到浏览器,笔误?)。服务器(译注:浏览器,笔误?)在收到该块后解压缩该文件,并执行所需要的操作(比如,执行一次realloc操作)。

poc.py中的construct_payload方法执行了该过程,并且将payload的大小减少到大约18MB。

资源使用

至少在理论上,exploit需要非常大量的内存:

  • 一个保存我们payload脚本的8GB的缓冲区。实际上,更像是12GB,因为在最后的realloc期间,有4GB空间必须得拷贝到一个新的8GB空间中。
  • 需要JavaScript申请大量的(大约6GB)缓冲区来填充由realloc创建的内存块。
  • 大约256 MB的ArrayBuffers。

不管怎么说,因为许多的缓冲区并没有被写入,所以不一定得消耗如此多的物理空间。更多的,在最终realloc中,只有4GB新的缓冲区会被写入之前释放的旧的缓冲区中,因此真正需要的仅仅是8GB而已。

不过这还是非常的占内存。然而,如果物理内存降低后,还是有一些技术来帮助减少内存占用:

  • 内存压缩(macOS):大量的内存区域可以被压缩然后交换至交换分区。这对于我们的案例来讲是完美的,因为8GB的缓冲区将完全被0填充。这个效果可以在Activity Monitor.app中观察到,在利用代码运行期间的某些时候会显示超过6GB的内存为“压缩”。
  • 页重复数据删除(Windows,Linux):具有相同内容的的内存页会被映射为具有写时复制(COW,copy-on-write)属性的相同物理页面(基本上将内存使用减少到4KB)。

CPU使用率在峰值期间(解压缩数据)也是相当的高。然而可以通过延迟发送较小的块之间的时间(这显然会增加漏洞利用的时间)来进一步降低CPU的压力。这也将给OS更多的时间来压缩和或删除大的重复数据内存缓冲区。

进一步可能的改进

目前的漏洞利用有几个不可靠的因素,主要是处理时机:

  • 在发送payload数据期间,如果JavaScript在浏览器完全处理下一个块之前运行分配,则分配是不同步的。这可能导致利用失败。理想情况下,JavaScript将在接收和处理下一个块后立即执行分配,这个或许可以通过观察CPU使用情况来确定。
  • 如果垃圾回收在我们已经破坏FreeSpan之后但在我们修复之前运行,崩溃!
  • 如果在我们释放了一些数组缓冲区之后,在触发了溢出之前,如果一个压缩的垃圾回收循环运行,那么漏洞利用就会失败,因为Arena将再次被填满。
  • 如果假的单元块恰好放在释放的ArrayBuffer的单元块内,那么我们的漏洞利用将失败,浏览器会在下一个垃圾回收周期中崩溃。每个Arena有25个单元块,则在理论上有1/25失败的几率。然而,在我的实验中,未使用单元块总是位于相同的偏移处(Arena的1216字节处),表明在开发阶段引擎的状态是相当确定的(至少关于Arena所持有的160字节的对象是来说是这样的)。

从我的经验来看,如果浏览器没有大量的处理任务,这个漏洞利用率非常可靠(>95%)。如果10个以上的其他选项卡是打开的,则漏洞利用仍然有效,但如果大型Web应用程序正在加载,则可能会失败。

结论

虽然从攻击者的角度来看,这个漏洞并不理想,但它仍然可以相当可靠地在低带宽下利用。过程中有趣的是使用的各种技术(压缩,页重复删除。。。)来使得这个漏洞更加简单的被利用。

考虑到如何防止这种错误的可利用性,有几点是我想说明的。一个相当通用的缓解措施是使用保护页面(无论用什么来访问保护页面都会产生段错误)。保护页面必须在每个mmap分配区域之前或之后分配,并且此举将防止对这种线性溢出的利用。但是,它们不会防止非线性溢出,比如说这个漏洞。另一种可能性是引入内部mmap随机化来分散整个地址空间中的分配区域(可能仅在64位系统上有效)。这最好由内核执行,当然也可以在用户空间中完成。


原文链接:https://saelo.github.io/posts/firefox-script-loader-overflow.html
译者:zplusplus于2017-3-23



翻译的时候用的是markdown,这是复制到浏览器之后最好的效果了,没有高亮。。。。。。


[培训]《安卓高级研修班(网课)》月薪三万计划,掌 握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

上传的附件:
收藏
免费 1
打赏
分享
最新回复 (3)
雪    币: 35399
活跃值: (19020)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
kanxue 8 2017-3-23 17:54
2
0
辛苦,翻译小组最近很给力!准备找一些图书给大家。
雪    币: 689
活跃值: (427)
能力值: ( LV11,RANK:190 )
在线值:
发帖
回帖
粉丝
zplusplus 1 2017-3-23 18:20
3
0
kanxue 辛苦,翻译小组最近很给力!准备找一些图书给大家。
不辛苦,既然加入小组了尽一份力就是应该的
雪    币: 6103
活跃值: (1207)
能力值: (RANK:30 )
在线值:
发帖
回帖
粉丝
CCkicker 2017-3-23 18:22
4
0
kanxue 辛苦,翻译小组最近很给力!准备找一些图书给大家。
游客
登录 | 注册 方可回帖
返回