Outils open-source des attaquants exploitant Ivanti CSA

Rédigé par Maxence Fossat - 12/05/2025 - dans CSIRT - Téléchargement

Dans le cadre de réponses à incident récentes où la cause initiale était la compromission d'une appliance Ivanti CSA, le CSIRT Synacktiv a rencontré plusieurs outils open-source utilisés par les attaquants. Cet article explore chacun de ces outils, leurs fonctionnalités, et aborde des moyens efficaces de les détecter.

Vous souhaitez améliorer vos compétences ? Découvrez nos sessions de formation ! En savoir plus

Introduction

En septembre et octobre 2024, Ivanti publie plusieurs1 avis2 de sécurité3 sur des vulnérabilités dans son produit Cloud Services Appliance (CSA) permettant le contournement des politiques de sécurité ainsi que l’exécution de code à distance. Il est ensuite révélé par une recherche de FortiGuard Labs Threat Research4 que certains groupes malveillants exploitent activement ces failles depuis au moins le 9 septembre 2024, soit avant toute publication d’avis de sécurité ou de correctif par Ivanti.

Dans certaines de ces compromissions, bien que l’accès initial ait résulté de l’exploitation de vulnérabilités zero-day, les étapes suivantes peuvent manquer de sophistication. Pour du mouvement latéral, extraire des identifiants ou établir une persistance, certains attaquants n'hésitent pas à utiliser des outils connus et des charges utiles peu discrètes.

Le CSIRT Synacktiv a récemment mené plusieurs investigations numériques où la source de la compromission était un appliance CSA vulnérable exposée sur internet. Au cours de ces missions, nous avons identifié un ensemble d’outils open-source employés par les attaquants pour atteindre leurs objectifs. Cet article propose une analyse de cette boîte à outils open-source utilisée dans le cadre d'exploitations d'Ivanti CSA et propose des règles de détection associées.

Tunnels

suo5

Après avoir compromis l’appliance Ivanti CSA, l’attaquant est parvenu à se déplacer latéralement vers un serveur Exchange exposé sur internet, utilisé pour les connexions OWA. Il y a implanté un tunnel proxy HTTP nommé suo5. Ce dernier, disponible publiquement sur GitHub depuis un premier commit datant de février 2023, se présente comme une alternative plus performante à d’autres outils de tunnel réseau tels que reGeorg et Neo-reGeorg.

Fait intéressant : le webshell personnalisé GLASSTOKEN, utilisé lors d’intrusions liées à l’exploitation de vulnérabilités zero-day dans Ivanti Connect Secure VPN5 en janvier 2024, était basé sur l’outil Neo-reGeorg. Par ailleurs, reGeorg a été utilisé comme webshell générique lors des attaques liées aux failles zero-day de Microsoft Exchange6 en mars 2021.

Le webshell suo5.aspx a été déployé sur le serveur Exchange à l’emplacement suivant :
C:\Program Files\Microsoft\Exchange Server\V15\FrontEnd\HttpProxy\owa\auth\OutlookEN.aspx. Ce nom pourrait faire référence aux activités liées à HAFNIUM pendant la phase de post-exploitation des vulnérabilités affectant Microsoft Exchange7 en mars 2021.

Fonctionnalités

Pour utiliser suo5, un serveur web accessible par l’attaquant (généralement exposé à internet) doit exécuter le code serveur de suo5, disponible en .NET, Java ou PHP (en version expérimentale). Dans cet exemple, la page OutlookEN.aspx exécutée par le composant IIS du serveur Exchange correspond à la version .NET du code serveur de suo5.

Du côté de l’attaquant, un proxy SOCKS5 doit être mis en place. Le binaire suo5 communique alors avec le serveur pour transmettre des données TCP encapsulées dans une communication HTTP(S).

$ ./suo5 -t https://mail.corporation.local/owa/auth/OutlookEN.aspx
[INFO] 02-27 13:28 header:
User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.1.2.3
Referer: https://mail.corporation.local/owa/auth/
[INFO] 02-27 13:28 method: POST
[INFO] 02-27 13:28 connecting to target https://mail.corporation.local/owa/auth/OutlookEN.aspx
[INFO] 02-27 13:28 got data offset, 0
[WARN] 02-27 13:28 the target may behind a reverse proxy, fallback to HalfDuplex mode
[INFO] 02-27 13:28 starting tunnel at 127.0.0.1:1111

[Tunnel Info]
Target:  https://mail.corporation.local/owa/auth/OutlookEN.aspx
Proxy:   socks5://127.0.0.1:1111
Mode:    half

[INFO] 02-27 13:28 creating a test connection to the remote target
[INFO] 02-27 13:28 start connection to 127.0.0.1:0
[INFO] 02-27 13:28 successfully connected to 127.0.0.1:0
[INFO] 02-27 13:28 connection closed, 127.0.0.1:0
[INFO] 02-27 13:28 congratulations! everything works fine
[INFO] 02-27 13:28 start connection to 192.168.123.15:22
[INFO] 02-27 13:28 successfully connected to 192.168.123.15:22

En utilisant un outil comme proxychains, l’attaquant peut alors communiquer avec n’importe quel système accessible par le serveur Exchange. Cela lui permet d’établir un point d’ancrage dans le réseau interne de la victime. Dans notre exemple, suo5 nous permet par exemple de nous connecter à un système situé sur un autre segment réseau (192.168.123.15), qui aurait autrement été inaccessible :

$ proxychains ssh victim@192.168.123.15
[proxychains] config file found: /etc/proxychains4.conf
[proxychains] preloading /usr/lib/x86_64-linux-gnu/libproxychains.so.4
[proxychains] DLL init: proxychains-ng 4.16
[proxychains] Strict chain  ...  127.0.0.1:1111  ...  192.168.123.15:22  ...  OK
victim@192.168.123.15's password:
Linux lab-victim 6.1.0-28-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.119-1 (2024-11-22) x86_64

L’article de blog lié à l’outil8 indique comment suo5 parvient à améliorer ses performances par rapport à d'autres outils : il utilise la directive chunked dans l’en-tête HTTP/1.1 Transfer-Encoding9. Cela signifie que l’expéditeur de la requête peut maintenir la connexion ouverte jusqu’à ce qu’il signale que le message est entièrement transmis. Dans ce mode, tous les paquets TCP encapsulés sont envoyés via une seule connexion HTTP, ce qui réduit la surcharge liée à de multiples requêtes et réponses HTTP. Il s'agit du mode "full duplex" mis en avant dans le README du projet sur GitHub :

Graphique illustrant une connexion suo5 full duplex. Sur le côté « local », l'utilisateur effectue des lectures et écritures TCP avec un gestionnaire SOCKS5. Le gestionnaire SOCKS5 communique (lecture/écriture de données) au moyen d'une seule connexion HTTP chunked avec un « pont proxy » situé sur le côté « distant ». Ce « pont proxy » échange à son tour des données TCP (lecture/écriture) avec un « serveur interne » sur ce même côté « distant ».
Mode full duplex de suo5 (source: https://koalr.me/posts/suo5-a-hign-performace-http-socks/).

En raison de certaines limitations dans le traitement des requêtes HTTP en .NET10, le mode "full duplex" ne peut pas être utilisé dans la version .NET du webshell. L’outil bascule alors en mode "half duplex", où seule la réponse est envoyée sous forme de flux de données continu, tandis que les requêtes sont envoyées individuellement. Le mode "half duplex" a également pour but de contourner certaines restrictions induites par l'utilisation d'un reverse proxy Nginx qui serait placé entre le client suo5 et le code serveur. Par défaut, Nginx met les requêtes dans un buffer11 et cette contrainte ne peut être contournée sans modifier la configuration du reverse proxy. Cependant, Nginx met également en buffer les réponses, sauf si l’en-tête X-Accel-Buffering: no est inclus dans la réponse12 (ce qui est le cas pour le webshell suo5). Malgré ces limitations, la connexion obtenue offre de meilleures performances que Neo-reGeorg :

Schéma illustrant une connexion suo5 half duplex. Côté « local », l'utilisateur communique en TCP (lecture/écriture) avec un gestionnaire SOCKS5. Celui-ci envoie ensuite plusieurs corps de requêtes HTTP à un « pont proxy » situé côté « distant ». En retour, il reçoit de ce pont un unique corps de réponse HTTP chunked. Le « pont proxy », de son côté, communique en TCP (lecture/écriture) avec un « serveur interne » distant.
Mode half duplex de suo5 (source: https://koalr.me/posts/suo5-a-hign-performace-http-socks/).

Détection

Pour détecter le fichier côté serveur (qu’il soit présent sur le disque ou uniquement chargé en mémoire), nous souhaitons créer une règle YARA qui évite d’utiliser des éléments facilement modifiables, comme les noms de fonctions ou de variables. L’objectif est de se concentrer sur la détection des fonctionnalités propres au webshell. Un indicateur de compromission contextuel que nous pourrions toutefois exploiter est le champ User-Agent, utilisé ici comme une sorte de « mot de passe » permettant uniquement aux clients suo5 autorisés d’accéder au webshell. Des attaquants négligents pourraient toutefois laisser la valeur par défaut :

Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.1.2.3

Il s’agit du User-Agent d’un navigateur Chrome (version 109.1.2.3) sur un smartphone LG Nexus 5 (Android 6.0 - Marshmallow).

Des attaquants plus discrets modifieront le User-Agent vérifié par le serveur :

private bool checkAuth()
{
    string ua = Request.Headers.Get("User-Agent");
    if (ua == null || !ua.Equals("CUSTOM_USER_AGENT"))
    {
        return false;
    }

Ils passeront alors cette chaîne User-Agent en argument au client suo5, via le paramètre --ua :

$ ./suo5 --target https://mail.corporation.local/owa/auth/OutlookEN.aspx --ua "CUSTOM_USER_AGENT"

De par la nature de cette chaîne de caractères — dont le but est de discriminer d'autres clients suo5 ou d'empêcher les connexions accidentelles au webshell — il y a de fortes chances qu’elle soit relativement unique, ce qui constitue un IOC intéressant pour les analystes DFIR et les équipes de SOC.

La règle YARA suivante cible plusieurs caractéristiques clés du webshell :

  • utilisation du User-Agent par défaut ;
  • présence de l’en-tête de réponse X-Accel-Buffering: no ;
  • authentification basée sur le User-Agent ;
  • utilisation d'un client TCP pour relayer les communications ;
  • sérialisation et désérialisation binaire ;
  • XORing d'octets et génération aléatoire de clés.
rule SYNACKTIV_WEBSHELL_ASPX_Suo5_May25 : WEBSHELL COMMODITY FILE
{
    meta:
        description = "Detects the .NET version of the suo5 webshell"
        author = "Synacktiv, Maxence Fossat [@cybiosity]"
        id = "d30a7232-f00b-45ab-9419-f43b1611445a"
        date = "2025-05-12"
        modified = "2025-05-12"
        reference = "https://www.synacktiv.com/en/publications/open-source-toolset-of-an-ivanti-csa-attacker"
        license = "DRL-1.1"
[...]
        score = 75
        tags = "WEBSHELL, COMMODITY, FILE"
        tlp = "TLP:CLEAR"
        pap = "PAP:CLEAR"

    strings:
        $user_agent = ".Equals(\"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.1.2.3\")" ascii    // default User-Agent
        $header = "Response.AddHeader(\"X-Accel-Buffering\", \"no\")" ascii    // X-Accel-Buffering response header
        $xor = /= \(byte\)\(\w{1,1023}\[\w{1,1023}\] \^ \w{1,1023}\);/    // XOR operation

        // suspicious functions
        $s1 = "Request.Headers.Get(\"User-Agent\")" ascii
        $s2 = "if (Request.ContentType.Equals(\"application/plain\"))" ascii
        $s3 = "Response.ContentType = \"application/octet-stream\";" ascii
        $s4 = "= Request.BinaryRead(Request.ContentLength);" ascii
        $s5 = "= Response.OutputStream;" ascii
        $s6 = "new TcpClient()" ascii
        $s7 = ".BeginConnect(" ascii
        $s8 = ".GetStream().Write(" ascii
        $s9 = "new BinaryWriter(" ascii
        $s10 = "new BinaryReader(" ascii
        $s11 = ".ReadBytes(4)" ascii
        $s12 = "BitConverter.GetBytes((Int32)" ascii
        $s13 = "BitConverter.ToInt32(" ascii
        $s14 = "Array.Reverse(" ascii
        $s15 = "new Random().NextBytes(" ascii

    condition:
        filesize < 100KB and ( $user_agent or ( ( $header or $xor ) and 8 of ( $s* ) ) or 12 of ( $s* ) )
}

Les analystes souhaitant mettre en place une règle IDS/IPS pour détecter le trafic HTTPS généré par suo5 n'auront probablement pas de solution simple à leur disposition. À moins d’intercepter le trafic TLS, la meilleure option aurait été de détecter les empreintes JA313/JA414 spécifiques au client suo5. Malheureusement, celui-ci utilise la fonctionnalité Randomized TLS Client Hello Fingerprint de la bibliothèque Go uTLS15 :

uTlsConn := utls.UClient(conn, &utls.Config{
    InsecureSkipVerify: true,
    ServerName:         hostname,
    MinVersion:         utls.VersionTLS10,
    Renegotiation:      utls.RenegotiateOnceAsClient,
}, utls.HelloRandomizedNoALPN)

iox

L’attaquant souhaitait conserver une porte d'entrée au sein du réseau de la victime, même après l’application des correctifs sur l’appliance Ivanti CSA. Il a donc déployé un autre tunnel nommé iox, directement sur l’appliance exposée sur internet.

Sur sa page GitHub, iox est présenté comme une meilleure alternative à deux autres outils de redirection de ports : lcx (également connu sous le nom de HTran16, pour HUC Packet Transmit Tool) et ew (ou Earthworm17).

L’outil lcx fait partie de l’histoire de la culture du hacking chinois du début des années 2000. Il s’agit d’un outil de redirection de ports, créé vers 200318 par un hacker chinois connu sous le pseudonyme de "Li0n", fondateur du groupe de hackers nationalistes Honker’s Union of China (HUC)19. Au fil des années, il a été utilisé20 dans de nombreuses21 attaques22 attribuées à des threat actors chinois23.

L’outil Earthworm a été conçu comme un remplacement plus puissant de lcx/HTran. Il reprend les concepts de base de lcx (différents modes de redirection de ports) tout en y ajoutant des fonctionnalités de proxy SOCKS5 et de proxy SOCKS5 inversé. Sa polyvalence en faisait un outil intéressant pour mettre en place des chaînes de redirection complexes, en rajoutant une couche d'abstraction sur la connexion entre une source et une machine cible via plusieurs sauts, et en tirant parti des quelques ports ouverts sur chaque pare-feu croisé sur le chemin24. Il a été utilisé comme outil de tunneling standard dans diverses attaques, comme l’exploitation active de la vulnérabilité ProxyShell par le groupe UNC298025, ou encore lors de l’attaque contre les infrastructures critiques américaines menée par Volt Typhoon26.

Fonctionnalités

iox dispose de fonctionnalités similaires à Earthworm, avec une syntaxe plus épurée et certaines optimisations au niveau du code réseau. Il ajoute également la possibilité de chiffrer le trafic, ainsi que la prise en charge du trafic UDP.

Les capacités de redirection de ports d’iox peuvent être utilisées pour contourner les restrictions de flux réseau (en passant par des ports connus et de confiance) et pour masquer la véritable origine des connexions réseau une fois qu’un point d’accès a été établi dans le réseau local (LAN) de la victime. Dans l’exemple suivant, le serveur en périphérie (deuxième saut — saut2) peut communiquer avec des hôtes externes via le port 443, et établit donc une connexion avec le premier saut (saut1). Le troisième saut de la chaîne (saut3) héberge un proxy SOCKS5. Du point de vue du réseau de la victime, les requêtes émanant de l’attaquant semblent provenir de ce troisième saut (saut3).

Illustration d'une chaîne de proxy iox : L'attaquant (proxychains ssh IP_VICTIME) se connecte à saut1 (iox fwd -l 1080 -l *443 -k CLE_HEXA). Hop 2 (iox fwd -r *IP_SAUT1:443 -r *IP_SAUT3:4242 -k CLE_HEXA) se connecte à saut1 (via le port chiffré *443, à travers un pare-feu) et à saut3. saut3 (iox proxy -l *4242 -k CLE_HEXA) reçoit la connexion de saut2 et la relaie vers la cible finale. L'ensemble du trafic entre les relais est chiffré (ports précédés de *)
Exemple des capacités de proxy et de redirection de port d'iox.

Les communications redirigées à l’intérieur du réseau interne de la victime sont chiffrées à l’aide de l’algorithme symétrique XChaCha20. Ce chiffrement n’a pas pour but d’assurer une confidentialité totale du contenu transféré, mais sert plutôt de technique d’évasion contre les systèmes NIDS/NIPS.

Un proxy SOCKS5 inversé peut également être mis en place. Dans le scénario suivant, le trafic provenant de la machine de l’attaquant semble provenir du second saut (saut2).

Connexion RDP via proxychains et deux relais iox : L'attaquant lance proxychains xfreerdp /v:IP_VICTIME vers saut1 (iox proxy -l 443 -l 1080). saut2 (iox proxy -r saut1:443) traverse un pare-feu pour se connecter à saut1 (sur le port 443), puis relaie la connexion RDP vers la cible finale (IP_VICTIME)
Exemple des capacités de proxy SOCKS5 inverse d'iox.

Dans ce scénario, le chiffrement du trafic — bien que disponible — n’est pas activé.

Détection

Les règles de détection publiques existantes reposent généralement sur les noms de fonctions et de bibliothèques27, ou sur les arguments passés en ligne de commande28. Afin de proposer des règles complémentaires à la communauté open-source, nous avons travaillé sur des règles de détection basées sur d’autres marqueurs.

Au cours de notre mission, nous avons identifié un échantillon obfusqué d’iox. Les noms de fonctions, les bibliothèques, les arguments ainsi que la majorité des chaînes de caractères avaient été supprimés ou obfusqués. Nous avons donc travaillé sur une logique de détection s’appuyant sur des motifs distinctifs présents dans le code. Il nous fallait des fonctions « maison » dont la logique ne repose pas sur les bibliothèques standard. Nous nous sommes concentrés sur la bibliothèque iox/crypto développée spécifiquement pour l’outil.

Deux motifs de code ont été sélectionnées pour notre logique de détection :

if len(key) < 0x20 {
    var c byte = 0x20 - byte(len(key)&0x1F)

dans la fonction ExpandKey et

bs[i] ^= byte(i) ^ bs[(i+1)%len(bs)]*((bs[len(bs)-1-i]*bs[i])%255)

dans la fonction shuffle.

L’extraction de petits blocs de code assembleur à partir de l’échantillon désassemblé permet d’identifier les motifs suivants à détecter :

00000000004BFA73  48 8B 9C 24 88 00 00 00        mov    rbx, [rsp+88h]
00000000004BFA7B  48 83 FB 20                    cmp    rbx, 20h
00000000004BFA7F  0F 8D 87 02 00 00              jge    loc_4BFD0C
00000000004BFA85  48 89 DE                       mov    rsi, rbx
00000000004BFA88  48 83 E3 1F                    and    rbx, 1Fh
00000000004BFA8C  83 C3 E0                       add    ebx, 0FFFFFFE0h
00000000004BFA8F  F7 DB                          neg    ebx

et

00000000004BF92C  44 0F B6 0C 07                 movzx  r9d, byte ptr [rdi+rax]
00000000004BF931  45 0F AF C8                    imul   r9d, r8d
00000000004BF935  41 BA FF FF FF FF              mov    r10d, 0FFFFFFFFh
00000000004BF93B  45 0F B6 DA                    movzx  r11d, r10b
00000000004BF93F  41 0F B6 C1                    movzx  eax, r9b
00000000004BF943  41 89 D1                       mov    r9d, edx
00000000004BF946  31 D2                          xor    edx, edx
00000000004BF948  66 41 F7 F3                    div    r11w
00000000004BF94C  41 0F AF D1                    imul   edx, r9d
00000000004BF950  31 CA                          xor    edx, ecx
00000000004BF952  41 31 D0                       xor    r8d, edx
00000000004BF955  44 88 04 0F                    mov    [rdi+rcx], r8b

Afin d’améliorer l’efficacité de notre règle, tout en limitant les faux positifs et en conservant de bonnes performances, nous avons structuré la détection autour de motifs d’instructions assembleur indépendants des registres utilisés. Par exemple, l’instruction cmp rbx, 20h pourrait utiliser n’importe quel registre prévu pour les arguments entiers et les résultats, tels que spécifiés dans l’ABI interne de Go29 : RAX, RBX, RCX, RDI, RSI, R8, R9, R10, R11. En veillant à utiliser autant que possible des motifs d'une longueur de 4 octets (afin d’améliorer les performances avec le moteur YARA/YARA-X), nous obtenons la chaîne hexadécimale suivante :

( 48 83 F8 20 | 48 83 FB 20 | 48 83 F9 20 | 48 83 FF 20 | 48 83 FE 20 | 49 83 F8 20 | 49 83 F9 20 | 49 83 FA 20 | 49 83 FB 20 )

La règle de détection finale, compatible YARA et YARA-X, est la suivante :

rule SYNACKTIV_HKTL_Tunnel_X64_GO_Iox_May25 : COMMODITY FILE
{
    meta:
        description = "Detects the 64-bits version of the iox tunneling tool used for port forwarding and SOCKS5 proxy"
        author = "Synacktiv, Maxence Fossat [@cybiosity]"
        id = "0b5a4689-58ea-45d5-aa14-a1455276352a"
        date = "2025-05-12"
        modified = "2025-05-12"
        reference = "https://www.synacktiv.com/en/publications/open-source-toolset-of-an-ivanti-csa-attacker"
        license = "DRL-1.1"
[...]
        score = 75
        tags = "COMMODITY, FILE"
        tlp = "TLP:CLEAR"
        pap = "PAP:CLEAR"

    strings:
        $expand_key = {
            ( 48 8B 84 24 | 48 8B 9C 24 | 48 8B 8C 24 | 48 8B BC 24 | 48 8B B4 24 | 4C 8B 84 24 | 4C 8B 8C 24 | 4C 8B 94 24 | 4C 8B 9C 24 ) ?? ?? ?? ??
            ( 48 83 F8 20 | 48 83 FB 20 | 48 83 F9 20 | 48 83 FF 20 | 48 83 FE 20 | 49 83 F8 20 | 49 83 F9 20 | 49 83 FA 20 | 49 83 FB 20 )
            ( 0F 8D ?? ?? ?? ?? | 7D ?? )
            ( 48 89 ?? | 49 89 ?? | 4C 89 ?? | 4D 89 ?? )
            ( 48 83 E0 1F | 48 83 E3 1F | 48 83 E1 1F | 48 83 E7 1F | 48 83 E6 1F | 49 83 E0 1F | 49 83 E1 1F | 49 83 E2 1F | 49 83 E3 1F | 83 E0 1F | 83 E3 1F | 83 E1 1F | 83 E7 1F | 83 E6 1F | 41 83 E0 1F | 41 83 E1 1F | 41 83 E2 1F | 41 83 E3 1F )
            ( 83 C0 E0 | 83 C3 E0 | 83 C1 E0 | 83 C7 E0 | 83 C6 E0 | 41 83 C0 E0 | 41 83 C1 E0 | 41 83 C2 E0 | 41 83 C3 E0 )
            ( F7 D8 | F7 DB | F7 D9 | F7 DF | F7 DE | 41 F7 D8 | 41 F7 D9 | 41 F7 DA | 41 F7 DB )
        }

        $shuffle = {
            ( 44 0F B6 04 | 44 0F B6 0C | 44 0F B6 14 | 44 0F B6 1C | 44 0F B6 44 | 44 0F B6 4C | 44 0F B6 54 | 44 0F B6 5C | 46 0F B6 04 | 46 0F B6 0C | 46 0F B6 14 | 46 0F B6 1C | 46 0F B6 44 | 46 0F B6 4C | 46 0F B6 54 | 46 0F B6 5C ) [1-2]
            ( 45 0F AF C0 | 45 0F AF C1 | 45 0F AF C2 | 45 0F AF C3 | 45 0F AF C8 | 45 0F AF C9 | 45 0F AF CA | 45 0F AF CB | 45 0F AF D0 | 45 0F AF D1 | 45 0F AF D2 | 45 0F AF D3 | 45 0F AF D8 | 45 0F AF D9 | 45 0F AF DA | 45 0F AF DB | 44 0F AF C0 | 44 0F AF C1 | 44 0F AF C2 | 44 0F AF C3 | 44 0F AF C8 | 44 0F AF C9 | 44 0F AF CA | 44 0F AF CB | 44 0F AF D0 | 44 0F AF D1 | 44 0F AF D2 | 44 0F AF D3 | 44 0F AF D8 | 44 0F AF D9 | 44 0F AF DA | 44 0F AF DB )
            [0-4]
            ( 41 B8 | 41 B9 | 41 BA | 41 BB ) FF FF FF FF
            ( 45 0F B6 C0 | 45 0F B6 C1 | 45 0F B6 C2 | 45 0F B6 C3 | 45 0F B6 C8 | 45 0F B6 C9 | 45 0F B6 CA | 45 0F B6 CB | 45 0F B6 D0 | 45 0F B6 D1 | 45 0F B6 D2 | 45 0F B6 D3 | 45 0F B6 D8 | 45 0F B6 D9 | 45 0F B6 DA | 45 0F B6 DB )
            [0-4]
            ( 41 89 D0 | 41 89 D1 | 41 89 D2 | 41 89 D3 | 89 D0 | 89 D1 | 89 D2 | 89 D3 )
            31 D2
            ( 66 41 F7 F0 | 66 41 F7 F1 | 66 41 F7 F2 | 66 41 F7 F3 )
            ( 41 0F AF D0 | 41 0F AF D1 | 41 0F AF D2 | 41 0F AF D3 | 44 0F AF C2 | 44 0F AF CA | 44 0F AF D2 | 44 0F AF DA | 0F AF D0 | 0F AF D1 | 0F AF D2 | 0F AF D3 | 0F AF C2 | 0F AF CA | 0F AF D2 | 0F AF DA )
            ( 31 ?? | 41 31 ?? | 44 31 ?? | 45 31 ?? )
            ( 31 ?? | 41 31 ?? | 44 31 ?? | 45 31 ?? )
            ( 44 88 04 ?F | 44 88 0C ?F | 44 88 14 ?F | 44 88 1C ?F | 44 88 04 ?7 | 44 88 0C ?7 | 44 88 14 ?7 | 44 88 1C ?7 | 88 04 ?F | 88 0C ?F | 88 14 ?F | 88 1C ?F | 88 04 ?7 | 88 0C ?7 | 88 14 ?7 | 88 1C ?7 | 44 88 04 3? | 44 88 0C 3? | 44 88 14 3? | 44 88 1C 3? | 88 04 3? | 88 0C 3? | 88 14 3? | 88 1C 3? )
        }

    condition:
        ( uint16be( 0 ) == 0x4d5a or uint32be( 0 ) == 0x7f454c46 or uint32be( 0 ) == 0xcffaedfe ) and filesize < 5MB and all of them
}

Cette règle a été testée avec YARA, YARA-X et yaraQA afin d’en évaluer les performances. Elle nous a permis de détecter d’autres échantillons obfusqués de l’outil circulant dans la nature.

Par souci d’exhaustivité, une autre règle — basée uniquement sur les chaînes présentes dans les échantillons non obfusqués — a également été créée :

rule SYNACKTIV_HKTL_Tunnel_GO_Iox_May25 : COMMODITY FILE
{
    meta:
        description = "Detects the iox tunneling tool used for port forwarding and SOCKS5 proxy"
        author = "Synacktiv, Maxence Fossat [@cybiosity]"
        id = "407d4f90-a281-4f0c-8d8e-ebe45217d3d9"
        date = "2025-05-12"
        modified = "2025-05-12"
        reference = "https://www.synacktiv.com/en/publications/open-source-toolset-of-an-ivanti-csa-attacker"
        license = "DRL-1.1"
[...]
        score = 75
        tags = "COMMODITY, FILE"
        tlp = "TLP:CLEAR"
        pap = "PAP:CLEAR"

    strings:
        $s1 = "Forward UDP traffic between %s (encrypted: %v) and %s (encrypted: %v)"
        $s2 = "Open pipe: %s <== FWD ==> %s"
        $s3 = "Reverse socks5 server handshake ok from %s (encrypted: %v)"
        $s4 = "Recv exit signal from remote, exit now"
        $s5 = "socks consult transfer mode or parse target: %s"

    condition:
        ( uint16be( 0 ) == 0x4d5a or uint32be( 0 ) == 0x7f454c46 or uint32be( 0 ) == 0xcffaedfe or uint32be(0) == 0xcefaedfe ) and filesize < 5MB and all of them
}

Mouvement latéral

atexec-pro

Le script atexec.py30 est un outil bien connu de mouvement latéral, basé sur les bibliothèques Impacket31, qui permet d’exécuter du code à distance sur une machine cible via le service du Planificateur de tâches Windows (Task Scheduler).

L’attaquant a exécuté des commandes sur le contrôleur de domaine Active Directory à partir d’un autre hôte présent sur le réseau, en utilisant une variante de ce script appelée atexec-pro.py32.

Fonctionnalités

Le script original atexec.py établit d’abord une connexion RPC via un named pipe SMB33, ciblant l'endpoint \pipe\atsvc34. Il utilise ensuite cette connexion pour se lier à l’interface ITaskSchedulerService35. Il crée alors une tâche planifiée, avec un nom composé de 8 caractères aléatoires, configurée pour exécuter une commande via l'invite de commande (cmd.exe) avec les privilèges les plus élevés disponibles (NT AUTHORITY\SYSTEM), et pour rediriger la sortie vers un fichier temporaire situé dans %WINDIR%\Temp\<NOM_TÂCHE>.tmp.

Le script déclenche l’exécution de la tâche, la supprime, lit le contenu du fichier .tmp via une connexion SMB au partage ADMIN$, puis supprime ce fichier. Cela signifie que le script communique uniquement avec le port 445 pendant toute son exécution.

Le script atexec-pro.py fonctionne de manière sensiblement différente. Tout d’abord, il propose une alternative à la connexion RPC via SMB, en s’appuyant sur RPC via TCP/IP36 pour manipuler les tâches planifiées. Ce protocole utilise le service RPC Endpoint Mapper, à l’écoute sur le port 135, pour résoudre dynamiquement le port (dans la plage haute, typiquement entre 49152 et 6553537) correspondant à l’interface ITaskSchedulerService. Bien que cette méthode constitue une alternative intéressante, la majorité des configurations de pare-feu Windows en environnement Active Directory bloquent par défaut les connexions entrantes vers ces ports dynamiques, ce qui rend cette méthode de connexion généralement inefficace.

Une fois la connexion établie, l’outil se présente comme un shell interactif :

$ python3 atexec-pro.py -i TSCH corporation.local/Administrator@192.168.122.71
[!] This will work ONLY on Windows >= Vista
Password:
[*] Connecting to DCE/RPC as corporation.local\Administrator
[*] Successfully bound.
[+] Type help for list of commands. 🚀
ATShell (Administrator@192.168.122.71)> help

Documented commands (use 'help -v' for verbose/'help <topic>' for details):

Run Command
===========
cmd_exec  ps_exec

Post Exploitation
=================
download  execute_assembly  upload

L’exécution de commandes à distance fonctionne alors comme suit :

La figure illustre une interaction entre un « hôte attaquant » (gauche) et un « hôte victime » (droite). Le script atexec-pro.py, depuis l'hôte attaquant, crée puis exécute une tâche planifiée sur l'hôte victime. Cette tâche exécute un script PowerShell (encodé en base64) qui récupère la description de la tâche, la déchiffre (AES-CBC), l'exécute via Invoke-Expression, chiffre le résultat (avec la même clé AES-CBC) et met à jour la description de la tâche avec ce résultat chiffré. Pour conclure, le script atexec-pro.py récupère à distance la description mise à jour et supprime la tâche.

L’exécution d’assemblies .NET fonctionne de manière similaire. Pour les opérations d’upload et de download, le contenu du fichier est encodé et lu ou inséré dans le champ Description de la tâche planifiée.

Les fonctionnalités disponibles dont donc les suivantes :

  • exécution de commandes via l'invite de commande ;
  • exécution de commandes via PowerShell ;
  • téléversement de fichiers ;
  • téléchargement de fichiers ;
  • exécution d'assemblies .NET ;
  • interaction avec ITaskSchedulerService par le biais de RPC via SMB ou de RPC via TCP/IP.

En raison du mécanisme utilisé pour le transfert de fichiers (via le champ Description), les fonctionnalités de téléversement, de téléchargement et d’exécution .NET sont limitées à des fichiers de 1 Mo maximum.

Détection

Ce qu'atexec-pro.py gagne en fonctionnalités, il le perd en discrétion. Chaque commande lancée via le Planificateur de tâches transite d’abord par un script PowerShell, comme défini dans le fichier XML de la tâche :

<Exec>
  <Command>powershell.exe</Command>
  <Arguments>-NonInteractive -enc {ps_command}</Arguments>
</Exec>

{ps_command} est remplacé par atexec-pro.py par un script PowerShell encodé en Base64. Les scripts par défaut utilisés pour chaque commande de l’outil peuvent facilement être considérés comme suspects, car ils incluent :

  • de l'encodage et de décodage Base64 ;
  • du chiffrement et du déchiffrement AES ;
  • l'interaction avec le service Planificateur de tâches via les Task Scheduler Scripting Objects38.

Ainsi, même avec une configuration par défaut des journaux Windows où le Script Block Logging n’est pas totalement activé (et seuls les scripts jugés suspects sont journalisés), des événements à l'Event ID 4104 sont générés dans Microsoft-Windows-PowerShell\Operational pour chaque commande exécutée par atexec-pro.

Cela permet la mise en place de la règle Sigma suivante :

title: atexec-pro - Suspicious PowerShell script
id: 8da0570e-adc3-4d2d-8acf-07f8cde5db3a
status: experimental
description: Suspicious PowerShell script contents related to execution of atexec-pro remote execution tool
license: DRL-1.1
references:
    - https://www.synacktiv.com/en/publications/open-source-toolset-of-an-ivanti-csa-attacker
author: Synacktiv, Maxence Fossat [@cybiosity]
date: 2025-05-12
modified: 2025-05-12
tags:
    - attack.execution
    - attack.t1053
    - tlp.clear
    - pap.clear
logsource:
    product: windows
    category: ps_script
    definition: Script Block Logging must be enabled
detection:
    selection_base:
        EventID: 4104
        ScriptBlockText|contains|all:
            - '[System.Convert]::ToBase64String('
            - '[System.Convert]::FromBase64String('
            - 'New-Object System.Security.Cryptography.AesManaged'
            - '[System.Security.Cryptography.CipherMode]::CBC'
            - '.CreateEncryptor()'
            - '.CreateDecryptor()'
            - 'New-Object -ComObject Schedule.Service'
            - '.GetTask('
            - '.RegistrationInfo.Description'
            - '.RegisterTaskDefinition('
    selection_script_cmd:
        ScriptBlockText|contains: 'iex'
    selection_script_upload:
        ScriptBlockText|contains: 'Set-Content -Path '
    selection_script_download:
        ScriptBlockText|contains: 'Get-Content -Path '
    selection_script_net:
        ScriptBlockText|contains|all:
            - '[System.Reflection.Assembly]::Load('
            - 'New-Object System.IO.StreamWriter'
            - '.Invoke('
            - 'New-Object System.IO.StreamReader('
    condition: selection_base and 1 of selection_script*
falsepositives:
    - Legitimate scripts using these cmdlets
level: high

Une règle similaire peut être construite à partir du contenu de la ligne de commande, à condition que la création de processus soit journalisée.

En ce qui concerne les artefacts forensiques laissés par l’exécution du script original atexec.py, l’article suivant les explore (ainsi que de nombreux autres outils d’exécution de code à distance ;).

Conclusion

Tout au long de cet article, nous avons étudié trois outils activement utilisés par des acteurs malveillants pour transférer du trafic au travers d'un réseau interne (suo5, iox) et pour exécuter du code à distance (atexec-pro). Nous avons analysé leurs fonctionnalités principales et en avons tiré des règles de détection YARA et Sigma, en nous concentrant sur une détection large, une meilleure résistance à l’évasion de défense et un faible taux de faux positifs.

Toutes les règles de détection créées dans le cadre de cet article seront maintenues dans le dépôt GitHub suivant.

Si votre entreprise a besoin d’assistance pour une levée de doute ou pour une réponse à un incident de sécurité, n’hésitez pas à contacter le CSIRT Synacktiv.