在Word或zip文档中嵌入一个指向powershell的快捷方式文件(.lnk),是一种已知的恶意软件隐蔽传播方法,就连涉嫌干扰美国大选的俄罗斯APT组织也经常使用这种方法(参考:Volexity、CrowdStrike)。例如,在奇幻熊(Fancy Bear)针对一些美国研究机构的网络渗透中,就使用了一种很少见的快捷方式文件后门攻击,通过该手段,攻击者不仅实现了powershell命令的嵌入执行,还能把整个payload存储在.lnk文件隐秘位置完成攻击调用,受害者一不小心点击了这种恶意的lnk文件,就会掉进攻击者布下的“陷阱”中。
对红方渗透人员来说,该攻击手段和相关样本的研究学习非常有用。本文中,我将展示如何制作“奇幻熊式”的恶意快捷方式文件(.lnk),通过该.lnk文件生成的恶意程序主体Dropper可应用于渗透测试场景中。该Dropper程序包含.lnk文件目标路径区域powershell、构造精巧的调用脚本和嵌入payload三个主要部分,结构流程如下图抽象所示:
规避目标系统的长度限制我们都知道,在Windows系统中创建快捷方式文件非常简单:桌面环境下,右键点击鼠标,“新建”,然后“快捷方式”,之后跳出一个要求输入对象位置的对话框,以此就完成了一次.lnk文件创建。之后,如下图所示,我们可以查看一下该lnk文件的属性特征,并尝试向其目标路径(target)中添加其它参数。
在.lnk文件属性下的目标路径区域(target)中,可以加入最多260个字符数。由于有字符数的限制,为了创建一个快捷方式后门,我们只能将lnk文件指向例如powershell的执行程序,并在其中加入一段很小的经过封装的命令作为执行参数。但是,要突破这种目标路径区域的最大字符限制,可以通过jscript创建的WScript Shell对象快捷方式来实现,该方式可以向目标路径区域添加达1096个字符数。扩展增加后的目标路径区域如下图十六进制编辑器中所示:
对Lnk格式文件的利(làn)用找到扩展增加目标路径区域字符数的方法,就可以实现向指向powershell中传递更多的执行参数,但对于熟谙.lnk文件格式的攻击者来说,这种方法还不足以执行足够大、足够精巧的调用脚本。
当一个快捷方式文件被创建之后,很多关于系统的元数据信息也被储存在其lnk文件中,例如驱动器标签、主机名称等:
当我研究了.lnk格式文件规范后发现,这些元数据都可能包含了一个不限长度的变量。就拿主机名称元数据来说,它可以储存一个任意长度的变量,而这点就可能被攻击者利用,用来嵌入任意payload,即目标路径区域内不允许执行的、有长度限制的payload,攻击者可以放到这里进行隐蔽存储和调用执行。
创建快捷方式后门“陷阱”文件有了以上这些信息之后,可以尝试制作一个恶意的快捷方式陷阱文件。在这里,我直接选用系统内置的powershell作为脚本编译语言。以下是对该恶意lnk文件三个主体部分的制作思路:
第一部分,我们需要构造一个调用payload的powershell脚本。该脚本被以指向powershell执行的编码参数形式,存储在lnk文件属性的目标路径区域(target)中,当受害者点击了lnk文件之后,该脚本将会被自动执行。这个精心构造的脚本最终将会调用存储在lnk文件主机名称元数据区域的payload。以下是这个脚本的典型示例:
实现代码:
#--Carving script: will find and decode the script block #--embedded in the shortcuts hostname $payloadStartIndexInShortcut=1000; $payloadSize=100; $shortcutFilename="interesting-title.lnk"; #create a byte array to store the encoded payload $encodedPayloadBytes=New-Object byte[]($payloadSize); #read the contents of the shortcut, starting from $payloadStart $lnk=New-Object IO.FileStream $shortcutFilename,'Open','Read','ReadWrite'; $lnk.Seek($payloadStartIndexInShortcut,[IO.SeekOrigin]::Begin); $lnk.Read($encodedPayloadBytes,0,$payloadSize); #Base64 decode encoded payload $decodedPayloadBytes=[Convert]::FromBase64CharArray($encodedPayloadBytes,0,$encodedPayloadBytes.Length); $scriptBlock=[Text.Encoding]::Unicode.GetString($decodedPayloadBytes); #execute payload (script block) iex $scriptBlock;