Saturday, January 21, 2023

Mm .. Malware Analysis

"All things are determined by external causes to exist and to act in a fixed and determinate way."—Baruch Spinoza

TL;DR: Analysis of malspam potentially targeting an organization. C#/.NET binary using KoiVM, process hollowing, and abusing vulnerable procexp152.sys driver.

Update 1: I've uploaded some reference material including the original malware sample, an encrypted (and decrypted) payload, a decryptor in Python, and decompiled tars of both stages to Github and VirusTotal.

Update 2: There's a possible third stage and some strings I glossed over. I've extracted and uploaded it to my github and will update this post if/when I break free from other work.

Earlier in January, I picked up a malware sample and thought I'd share this analysis. But I've been distracted with vulnerability research and almost forgot about this blog post. So, here's to 2023 and finishing more of the things I start. Apologies for the idiosyncratic timestamps.

Stage 1

This particular sample appears to be from a spam mail or spearphishing campaign, masquerading as "shipping documents."

One of the first things we notice about this particular binary is that it has been signed with one (1) Impact Hub certificate.

A forged cryptographic signature from ImpactHub

Analyzing the file with PEBear, we see it's a Portable Executable. So, out of sheer curiousity, having recently been learning about scripting in WinDbg, I tried to run it via WinDbg, just to see what would happen:

Response                         Time (ms)     Location
Deferred                                       srv*
Symbol search path is: srv*
Executable search path is: 
ModLoad: 0000023b`2f4a0000 0000023b`2f6c6000   MySQLInstaller.Core.exe
ModLoad: 00007ffc`53840000 00007ffc`53a49000   ntdll.dll
ModLoad: 00007ffc`3f580000 00007ffc`3f5e7000   C:\Windows\SYSTEM32\MSCOREE.DLL
ModLoad: 00007ffc`524f0000 00007ffc`525ae000   C:\Windows\System32\KERNEL32.dll
ModLoad: 00007ffc`50d90000 00007ffc`5110b000   C:\Windows\System32\KERNELBASE.dll
ModLoad: 00007ffc`4e260000 00007ffc`4e2f2000   C:\Windows\SYSTEM32\apphelp.dll
(1f20.116c): Break instruction exception - code 80000003 (first chance)
ntdll!LdrpDoDebuggerBreak+0x30:
00007ffc`5391cef4 cc              int     3

We hit our first breakpoint. But continuing after this, the malware interacted with several system components — then started throwing Common Language Runtime exceptions until eventually terminating altogether. The sample seemed to dislike the debugger, or have a deliberate anti-debugging feature. Further, we also saw calls to C:\Windows\assembly\NativeImages* .. though I've omitted the output here for brevity:

0:000> g
.. 
(1f20.116c): CLR exception - code e0434352 (first chance)
(1f20.116c): CLR exception - code e0434352 (first chance)
(1f20.116c): CLR exception - code e0434352 (first chance)
(1f20.116c): Unknown exception - code 02345678 (first chance)
(1f20.116c): Unknown exception - code 02345678 (first chance)
ntdll!NtTerminateProcess+0x14:

Opening Process Explorer and re-running our initial executable, we see some calls to user32.dll and kernel.dll, then cmstp.exe, the connection manager, launches the following:

`C:\windows\system32\cmstp.exe" /au C:\windows\temp\zv4qr2y1.inf`

Note, cmstp.exe is a signed Microsoft executable, and here it appears being used to proxy execution of malicious code. And immediately afterward, this file queries the Windows Local Security Authority. We can see this behavior from ProcMon.

Next, we receive a MessageBox masquarding as a legitimate interface, imitating a Corporate VPN. Clicking through, it uses Powershell to invoke a command making an exclusion for itself. We can also see this in ProcMon:

"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" Add-MpPreference -ExclusionPath "C:\Users\hexagr\Downloads\bcba671f1e3e1f7ab80f59d5c1cb280bfec3ca519b37820af0bb720fb5d7a8b9\bcba671f1e3e1f7ab80f59d5c1cb280bfec3ca519b37820af0bb720fb5d7a8b9.exe"

We notice the binary seems to make repeated calls back to itself. This is likely to reflectively load obfuscated code. We see hijacked processes pilfer through the registry, querying machine information, creating, and loading new values along the way. It also queries Windows Cryptographic components.

As the chain runs, we see several other binaries, powershell.exe, SMSvcHost.exe and jsc.exe, among others, get abused. In this case, jsc.exe became active and rummaged through system and network settings. It attempted to grab cryptographic certificates, browser data, saved logins, as well as VPN, e-mail, and FTP credentials, etc.

Initially, we see no obvious modifications to Autorun entries. I also double checked with PersistenceSniper. Instead, earlier in the infection chain, we note that dllhost was active. And later it appears to be used to load a CLSID. This is likely IFileOperation, part of the COM interface, being used to perform a UAC bypass, creating a DLL in a protected folder, e.g. side-loading a DLL:

C:\Windows\system32\DllHost.exe /Processid:{}

Additionally, some of the other logs made me suspect the malware was checking for vulnerable processes it could COM-hijack. But .. alas, I have no definitive evidence for this particular claim, only a hunch based on repeated queries to COM Objects after processes became infected.

As we saw earlier from the calls to "NativeImages" and the CLR errors that were thrown in WinDbg, it's safe to guess this malware is written in a .NET language. Perhaps C Sharp or VB. The flow is likely: C Sharp -> Common Intermediate Language -> Native code?

Using Wireshark, I tried to capture a sample of network behavior but it appears to use TLS. Still, we can nonetheless see the C2 it uses. In Wireshark, a call to a Telegram channel stands out. We also find calls to "ipify.org".

Returning to the sample with Ghidra, we see it is, in fact, written in a .NET language.

                             void .cctor(void)
             void                       
                             .NET CLR Managed Code
                             .cctor
        0040207b 2a              db[1]
        0040207b [0]            2Ah
        0040207c e2              MethodDe
        0040207c e2              db        E2h                     Size+Flags    

Knowing this, we can potentially learn more by attempting to decompile the initial binary into more legible code with a tool like ILSpy. I've cut the excerpt below for brevity because the malware author has authored their code such that it spans over 200 directories in order to make reversing harder. And furthermore, the source has had most of its variable names programmatically modified.

hexagr@am analysis % ilspycmd -p -o src/ "shipping documents.exe"
hexagr@am analysis % tree -a src
src
├── 0er
│   └── Defi0e.cs
├── 0uch
│   └── N8rrow.cs
├── 0wenty
│   └── L0nd.cs
├── 1aby
│   └── East8rn.cs
├── 1ariety
│   └── IPackage.cs
├── 1auce
│   └── Explo5e.cs
├── 1e
│   └── 5abit.cs
..
.. 

├── Wo6ld
│   └── A11ly.cs
├── Wr0t0ng
│   └── 4rror.cs
├── Yourse8f
│   └── 2ttitude.cs
└── bcba671f1e3e1f7ab80f59d5c1cb280bfec3ca519b37820af0bb720fb5d7a8b9.csproj

216 directories, 227 files

Success. Indeed, we are looking at a (mostly) C Sharp project. Away from dynamic analysis, we can glean a great deal of what this initial first stage binary does from the decompiled code here. We can check if our suspicion about deliberate anti-debugging features is true by looking for certain strings and methods. A lot of the functions seem to include "SetLastError," which can be used to precede entry points and halt debugging.

There are probably a dozen features which theoretically can be used for anti-debugging, just in this sample alone. I won't cover them here. And as we can patch registers, step over functions, and utilize various anti-anti-debugging tricks to get around such obstacles, it's not something I'm going to cover in depth in this blog post.

Now, a closer look. Shortly after the initial binary's entry point, we find a call to Bu6ch ..

.. which leads us to calling Disc7urse.E6en .. which is part of an AES routine. We'll come back to this when we discuss Stage 2.

Indeed, it would appear this is merely the first stage. But as even the first decompiled binary generates around 96k lines of code, we will probably not be able to cover every aspect of what this code does in this blog post. But I will try to highlight the .. gist of it (dad jokes, I will see myself out). Anyway .. we will skip over the encrypted blob for now.

But we should at least take note that, from the jump, our entry point ingests a base64 key and payload, and deciphers something:

public class Disc7urse
{
	private static byte[] Bi4d()
	{
		return Convert.FromBase64String("v3IX7rruIQ4xt+78MTJjH3zIoZkJl2BfmFQx0/x3m9g=");
	}

	public static string Critic7l(byte[] Carefu1)
	{
		using Aes aes = Aes.Create();
		aes.Key = Bi4d();
		aes.Mode = CipherMode.ECB;
		aes.Padding = PaddingMode.PKCS7;
		ICryptoTransform cryptoTransform = aes.CreateEncryptor();
		byte[] inArray = cryptoTransform.TransformFinalBlock(Carefu1, 0, Carefu1.Length);
		return Convert.ToBase64String(inArray);
	}

	public static byte[] E6en(string O33)
	{
		using Aes aes = Aes.Create();
		aes.Key = Bi4d();
		aes.Mode = CipherMode.ECB;
		aes.Padding = PaddingMode.PKCS7;
		ICryptoTransform cryptoTransform = aes.CreateDecryptor();
		byte[] array = Convert.FromBase64String(O33);
		return cryptoTransform.TransformFinalBlock(array, 0, array.Length);
	}

	public static string _7hange(string Afric2n)
	{
		using Aes aes = Aes.Create();
		aes.Key = Bi4d();
		aes.Mode = CipherMode.ECB;
		aes.Padding = PaddingMode.PKCS7;
		ICryptoTransform cryptoTransform = aes.CreateEncryptor();
		byte[] bytes = Encoding.ASCII.GetBytes(Afric2n);
		byte[] inArray = cryptoTransform.TransformFinalBlock(bytes, 0, bytes.Length);
		return Convert.ToBase64String(inArray);
	}

	public static string Sligh8ly(string Gra8uate)
	{
		using Aes aes = Aes.Create();
		aes.Key = Bi4d();
		aes.Mode = CipherMode.ECB;
		aes.Padding = PaddingMode.PKCS7;
		ICryptoTransform cryptoTransform = aes.CreateDecryptor();
		byte[] array = Convert.FromBase64String(Gra8uate);
		byte[] bytes = cryptoTransform.TransformFinalBlock(array, 0, array.Length);
		return Encoding.ASCII.GetString(bytes);
	}

But before we decrypt the second stage, let's highlight some more of the code we see in the initial binary. For example, one of the first queries we saw during the initial dynamic analysis in ProcMon was to LSA.

And though it's not very surprising, we do in fact actually see some DLL imports for querying Windows Local Security Authority in the initial sample, just a few lines of code away from our entry point.

We also find code to steal browser cookies, modify the registry, duplicate process handles, etc. For example, DuplicateHandle particularly tracks with the behavior we observed during the initial infection, e.g. signed Windows processes behaving nefariously, due to having their memory remapped e.g. process injection (hollowing):

We can further confirm this by using hasherezade's Hollow's Hunter tool to scan Windows for processes which have been remapped and injected into:

In the end, we see two things, a new 32 bit process, AddInProcess32.exe, and a suspicious COM object. Looking at the final binary under x64dbg, the memory mapped to AddInProcess32.exe seems to contain most of the classic stuff you'd find in off-the-shelf spyware, including features to access the Windows API for logging keystrokes, grabbing data from the clipboard, and so on.

While the first stage contains phishing code to imitate not only a Corporate VPN, but also several other legitimate-looking interfaces, the second stage pictured above, holds most of the calls that hook and abuse the Windows API for nefarious purposes. This is what is injected into the final process.

So, to recap: our sample appears to use a reflective loader and process injection for an initial foothold. Depending on the environment it detects, it prompts the user with a dialog. It then continues its flow, executing several PowerShell scripts, achieving process injection, and filelessly abusing MSBuild.exe to build a file used to infect "AddInProcess32.exe," which seems to hold most of the trojan code.

But after a reboot, I did not see AddInProcess32.exe again. But instead, a possible persistence mechanism stood out quite clearly—appearing here, a new registry entry linking to a vulnerable Windows driver, labeled here as "TaskKill," named with cryllic lettering. Hmm, I thought Microsoft claimed to have pushed updates to prevent this? Or perhaps not...

We also find some suspicious directory and registry accesses and artifacts. While some of these logs are likely from initial staging, I suspect more than one may be linked to persistence. The following registry classes and directories are of interest:

C:\Users\black\AppData\Local\Temp\*
C:\Users\black\AppData\LocalLow\Microsoft\CryptnetUrlCache\Content\*
C:\Users\black\AppData\Local\Temp\*
C:\Users\admin\AppData\Roaming\*
C:\Users\black\AppData\LocalLow\Microsoft\CryptnetUrlCache\Content\*
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Tracing\AddInProcess32_RASAPI32
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Tracing\*
HKEY_CLASSES_ROOT\Local Settings\MuiCache\*

Moreover, as we saw, this particular malware, "Agent Tesla" I believe(?), abuses a vulnerable Windows driver from Process Explorer. In its normal state, Process Explorer performs some low level tricks to observe, control, and manipulate Program Handles which are processed by the Windows kernel. Thus, the vulnerable driver lets attackers bypass security mechanisms usually employed by the kernel. Agent Tesla and other malware families abuse the strategy of "bringing your own vulnerable driver (BYOVD)" to run amok.

This is very likely why we didn't see any obvious modifications to the registry. Instead, I believe this particular malware sample employs the vulnerable procexp152.sys driver for the cause.

At least, I *think* this is a variant of Agent Tesla. This particular sample is uploaded to Virus Total here.

Stage 2

But wait! There's more .. well .. sort of. So, earlier, we skipped past our encrypted blobs and instead just reviewed the logic of the initial binary, and caught a glimpse of "AddInProcess32.exe"

Now let's go back to the AES routine we discovered earlier. We need a way to decrypt those base64 blobs. Here, I've drafted some Python for a decryption routine. There are various other encrypted strings, but I'll leave them as an exercise for the reader. :)

import os
import base64
import argparse
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes


def decrypt_AES(key, msg, padding_mode='CBC', iv=None):
    key = base64.b64decode(key)
    msg = base64.b64decode(msg)
    if not iv:
        iv = b'\x00' * 16
    else:
        iv = base64.b64decode(iv)

    if len(key) == 16:
        algo = algorithms.AES(key)
    elif len(key) == 24:
        algo = algorithms.AES(key)
    elif len(key) == 32:
        algo = algorithms.AES(key)
    else:
        raise ValueError("Invalid key size. Must be 16, 24, or 32 bytes.")

    if padding_mode == 'CBC':
        pad = modes.CBC(iv)
    elif padding_mode == 'ECB':
        pad = modes.ECB()
    else:
        raise ValueError("Invalid padding mode. Must be 'CBC' or 'ECB'.")

    cipher = Cipher(algo, pad, backend=default_backend())
    decryptor = cipher.decryptor()
    decrypted_data = decryptor.update(msg) + decryptor.finalize()
    return decrypted_data

def main():
    parser = argparse.ArgumentParser(description="Decrypt AES encoded messages")
    parser.add_argument("-key", "--key_file", required=True, help="File containing base64 encoded key")
    parser.add_argument("-msg", "--msg_file", required=True, help="File containing base64 encoded message")
    parser.add_argument("-output", "--output_file", help="File to write decrypted message to")
    parser.add_argument("-padding", "--padding_mode", required=True, default='CBC', help="Padding mode to use. Must be 'CBC' or 'ECB'. Default is 'CBC'.")
    args = parser.parse_args()
    key = open(args.key_file, 'r').read()
    msg = open(args.msg_file, 'r').read()
    decrypted_msg = decrypt_AES(key, msg, args.padding_mode)
    if args.output_file:
        with open(args.output_file, 'wb') as f:
            f.write(decrypted_msg)
    else:
        print(decrypted_msg)

if __name__ == '__main__':
    main()

Now we can deobfuscate the base64 we saw near the entry point and get a better understanding of how this is chained in with rest of the malware sample. Note that, before the decryption, we can see the some repeating bytes near the end of the file:

$ tail -f Hacking/Research/malware_blog0/stage2 | hexdump -C

0008cdf0  4a 37 68 30 54 4b 54 2f  68 47 68 56 46 42 45 6d  |J7h0TKT/hGhVFBEm|
0008ce00  54 32 79 46 61 43 65 34  64 45 79 6b 2f 34 52 6f  |T2yFaCe4dEyk/4Ro|
0008ce10  56 52 51 52 4a 6b 39 73  68 57 67 6e 75 48 52 4d  |VRQRJk9shWgnuHRM|
0008ce20  70 50 2b 45 61 46 55 55  45 53 5a 50 62 49 56 6f  |pP+EaFUUESZPbIVo|
0008ce30  4a 37 68 30 54 4b 54 2f  68 47 68 56 46 42 45 6d  |J7h0TKT/hGhVFBEm|
0008ce40  54 32 79 46 61 43 65 34  64 45 79 6b 2f 34 52 6f  |T2yFaCe4dEyk/4Ro|
0008ce50  56 52 51 52 4a 6b 39 73  68 57 67 6e 75 48 52 4d  |VRQRJk9shWgnuHRM|
0008ce60  70 50 2b 45 61 46 55 55  45 53 5a 50 62 49 56 6f  |pP+EaFUUESZPbIVo|
0008ce70  4a 37 68 30 54 4b 54 2f  68 47 68 56 46 42 45 6d  |J7h0TKT/hGhVFBEm|

$ python3 decrypt.py -key key -msg stage2 -padding ECB -output stage2_out

$ file stage2_out 
stage2_out: PE32+ executable (GUI) x86-64 Mono/.Net assembly, for MS Windows

So, we take this binary back to dnSpy and decompile it to see the actual source code .. and we learn it's .. KoiVM. Neat. This is the loader for the malware.

Essentially, KoiVM is a virtual machine that transposes the malicious source from the first binary, and remaps it into op codes only it understands. I won't go into detail about this here. But it is an effective method for evasion. The combination of a signed binary, process hollowing, virtualization/re-mapping of opcodes, and bringing their own vulnerable driver, means that the malware is (or .. was) much less likely to be noticed.

In the second executable, we find confirmation of what we saw in the "AddInProcess32.exe" x64dbg session. Relevant to process injection, ultimately to create processes in suspended states, unmap and remap memory. VirtualAlloc, GetThreadContext, ZwUnmapView, WriteProcessMemory, and NtResumeThread to execute the injected code, etc.

We also find code for loading and unloading the vulnerable driver we saw set in the registry. I'll upload the decompiled code, as well as the decryptor, to my Github soon after I reorganize the files from this analysis. And I'll probably update this post in the future to make some further remarks. Process Monitor logged a few hundred thousand registry events and .. I mostly just wanted to finish taking this sample apart in a more general sense.

Stage 3

I think this sample may contain a possible third stage .. potentially in the Resources section of the second binary. I've extracted this but haven't finished analyzing it yet, as the second stage is much more heavily obfuscated than the first binary. But nonetheless, I've uploaded the potential third stage to github. Below, we see some repeating bytes again from the end of the Resources file in stage2. I think this is potentially being XORd and/or shifted.

$ hexdump -C stage3-probable.bin
..
00029850  1e 67 ac 4a ec 5f bb 24  dc 0b 01 19 13 38 e5 8d  |.g?J?_?$?....8?.|
*
000298f0  1e 67 ac 4a ec 5f bb 24  dc 0b 01 19 13 38 d4 46  |.g?J?_?$?....8?F|
00029900  6c 81 b5 ea 2c 51 76 5c  db 59 82 4b c8 2c e5 8d  |l.??,Qv\?Y.K?,?.|
00029910  1e 67 ac 4a ec 5f bb 24  dc 0b 01 19 13 38 e5 8d  |.g?J?_?$?....8?.|
*
00029af0  1e 67 ac 4a ec 5f bb 24  dc 0b 01 19 13 38 ae 84  |.g?J?_?$?....8?.|
00029b00  50 6c a6 b1 8a fa 56 b4  53 87 a1 ed 6a 55        |Pl??.?V?S.??jU|

This sample appears similar to another "shipping documents" sample I saw on Malshare, which at least one signature allegedly attributes to Dragonfly APT. It also uses .NET and KoiVM.

But the sample I've remarked on in this post is much quieter than the sample on Malshare, which makes several calls to hastebin to retrieve various secondary payloads.

If you download this sample and notice anything interesting that I did not, or if I've made a grave error somewhere, or if you have any hints for improving analysis, let me know so I can update this post with corrections, and credit you in either case.

Here are a few potential indicators of compromise:

d95298befdde567b31571d16f327840fa0f0dd9c54bf876531820910418a52b6 8d3a48b6dae915f255007a6bb6eea2b377af25d7cf0af8898d318f5b16040463 9e01f705735418dc321445465adcc3a78be52e1de28c37037f1ed665df92de6f 881a337aa85a4b01c08706ab941573c5dc9b76ea0e4e1c2693a9b4aa4453ec8c

No comments:

Post a Comment