Context of this Entry
The context comes from Szabolcs Schmidt Tweet, where he shared a zip file named setup.pkg, that contains three files AsusMouseDriver.sys, Inx, kg.cmd:

Things to Setup : kg.cmd

Turns out Inx is a legit command line RAR utility that’ll extract contents from AsusMouseDriver.sys rar archive file in C:\Users\Public\WindowsSecurity folder.
Then gonna setup an AutoRun Key named UpdateService with command line set to execute ntoskrnl.exe with arguments being .\Lib\image, dcal143
Followed by running it immediately and displaying a decoy PDF created by thatgirlpossesed4773:

AsusMouseDriver.sys RAR archive contains legit files for Python3.10.11 environment like renamed python executable to ntoskrnl.exe and supported DLLs,
with only impostor Lib\image python file being ran with argument dcal143.
The Loader: Lib\image

(On the Left) we see image being python file, executes marshaled code-object after some b85 decode and bz2, zlib decompression on a huge buffer.
(On the Right) we’ll make a PYC file by prepending Python3.10 16-byte magic header to the marshaled code-object to further make sense of it,
The huge 74MB size, shows indication of obfuscation by junk insertion.
Trying to decompile the PYC file using pycdc or pylingual fails due to heavy variable name obfuscation and intentional use of opcodes that hinder decompilation, likely some protector is used:

Going through the pycdas disassembly output, after replacing the python co_varnames, co_names to placeholders, we can already make some guesses.
Interesting variable and function names like try_request_server, get_bytecodes, decrypt_code, rc4, base64, bz2, zlib, requests somewhat hints the behavior of this stage.
Starting off with enough imports, it imports libraries like sys, ctypes, marshal, zlib, base64, bz2, inspect, requests.

(684-692):id = sys.argv[1], meansdcal143is anid(694-714):ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 0), this will hide the console window

(2734-2736): declares theRC4decryptionKEY = b'phuongmai2005'(2738-2750): creates a functiontry_request_server()that takes an argument asip_server.

(4446-4460):exec(marshal.loads(get_bytecodes(b64code)), execute the loaded marhsal code-object
TL;DR This stage will try to execute a marshal code-object fetched from remote URL, after some
Base64decoding,Zlibdecompression, andRC4decryption.
As part of obfuscation, string constants are constructed dynamically with lots of lambda expression, which makes it kinda time taking to know the requested URL .
But some mock runs gives the payload URL cause python will complain when ran with disabled network access:

Going to the URL shows an active campaign, which they’re constantly updating with scripts, as of writing, most recent addition is dcal143v2.txt:


(On the Left-Bottom) We see marshal bytes, then we do same as previous ie. prepend magic header to form a new PYC file, then dump to further analyse.
The Injector : dcal143 Python Marshal-Object
Again this marshal code-object is obfuscated, and performs anti-decompilation measure.
Going through the variable and function names hints possible process hollowing behavior:

Walking the disassembly, we see it defines many helper functions:

(8214-8222): definesNtWriteVirtualMemoryfunction with argument and return types, for later use:
NtWriteVirtualMemory = ntdll.NtWriteVirtualMemory
NtWriteVirtualMemory.argtypes = [ wt.HANDLE, LPVOID, c_void_p, c_size_t, POINTER(c_size_t) ]
NtWriteVirtualMemory.restype = c_int(8224-8254): defines function likekillprocessbyid,runpe,shc_loaderand the names are self explanatory

(before-8366):base64_encrypted_pepayload is constructed(8368-8370): declaresRC4decryptionKEY = b'buiphuonglinh'
Summarizing the following ‘cause the disassembly was too long:
It’ll take a look at the current running processes then store them to process_list variable, following is the manual reconstruction of disassembly snippet:
process_list = subprocess.run(
['tasklist'], # string constructed dynamically
stdout=PIPE,
text=True,
creationflags=CREATE_NO_WINDOW
).stdoutthen will eventually compare’em against many antivirus process strings, if found then exits immediately.

(8478-8482): callsrun_pe(base64_encrypted_pe, KEY)that’ll inject the next payload to a remote process(8488-8492): then sleeps for a whiletime.sleep(20)

(8634-8638):shc_loader(base64_encrypted_shc, KEY)same goes to shellcode loader
TL;DR This stage will eventually inject next stage to a remote process, plus inject and run the shellcode to the same process.
We can get hold of Base64 encrypted shellcode and PE by performing some python injection using a tool called PyInjector:
This will write the contents of base64_encrypted_pe and base64_encrypted_shc variables the was constructed dynamically to external files.
run_pe : PE Injection into cvtres.exe
Starts system utility cvtres.exe in this case, cross-check its architecture, then uses combination of CreateProcessA, GetThreadContext, NtUnmapViewOfSection, VirtualAllocEx, NtWriteVirtualMemory, SetThreadContext API calls to perform injection:

Now when decoded and RC4 decrypted the content of base64_encrypted_pe, we get to the next stage which is .NET Reactor protected:

shc_loader: Injection to same process
This function uses VirtualAlloc, CreateThread, RtlMoveMemory, WaitForSingleObject, ResumeThread API calls to inject the shellcode in the same process, the x64dbg->Dump1 in following snap shows the shellcode being ran:

Again when decode and decrypted, we see this shellcode is generated using an open source tool donut:

The Donut Shellcode: Being Position-Independent
Taking a quick triage look with capa, we see this shellcode uses some aPLib data decompression, chaskey encryption and PEB access, all smells like donut innit? :

The following routine from the shellcode is a part of Donut’s chaskey decryption routine that decrypts the next stage for in-memory execution as AppDomain:

Above snippet bypasses Antimalware Scan Interface (AMSI) and Event Tracing for Windows (ETW) by patching related API like AmsiScanBuffer, AmsiScanString, EtwEventWrite, EtwEventUnregister, shows that the shellcode was generated with option -b (bypass) enabled (refer to donut github repo for more).
The Loader Again : Yxolp.exe The .Net Assembly
This stage will invoke mlBlFRODqNj9jxsZ5wv.LrNv6COPJYQa5wBkwqU -> KU8OtPPFaB() method from .NET assembly that is retrieved from the resource section:

The TripleDES key and IV are Base64 encoded:

The method being invoked:

Following emit will TripleDES decrypt and Zlib decompress the Hxyez resource from this assembly, we get to last stage that is again .NET Reactor protected DLL file:

The PureRAT Ft. ProtoBuf : Mvgnd.dll
We’ll use NETReactorSlayer to get comparatively cleaner version, I’ll go through some of the workings in particular:
This stage take use of Protocol Buffers (protobuf-net) for deserialization. One such buffer when deserialized reveals C2 server 151.242.170[.228] and ports 56001, 56002, 56003, 56004, 56005, 56007, along with another Base64 encoded buffer blob that decodes to an X509 certificate used for TLS pinning, followed by some other values (see right bottom of following snap):

Most of the strings are not decrypted by .NETSlayer, but we get the spirit of these snipppets:
above code performs socket setup with TLS pinning, for encrypted C2 communication,
Mutex creation used to make sure only one instance is running,
does persistence via registry key addition with binary value,
WMI query prolly checking any antivirus product presence,
capture full screenshot then does some conversion to JPEG to store them as an array, to eventually send them to C2 servers,
above snippet tries to keep itself running, no matter the exceptions occurred.
Also contains many more features like fingerprinting via GUID and system resources, capability to execute other plugins received from C2 server, looks for Crypto Wallet credentials from file system paths (%APPDATA%), and registry keys and so on.
The frequent use of deserialzed ProtoBuf object, along with other characteristics resembles of this being PureRAT/PXA Stealer (similarity found in other incidents). Many variable from python loader/injectors are in Vietnamese, but the C2 server is located in Singapore, It is possible that they’re using remote servers.
See ya in another post, till then have a nice time :)
IOC(s)
- source : https://x.com/smica83/status/1976718339314516476
setup.pkg:45f9b2a451141d50faf17513f0d78064716dc673a976e77da0bdf7fe27719106https[:]//104.194.153[.]193/homepage/links?id=dcal143Yxolp.exe.NET Assembly:3de860c805c65fadae42459736cfebd92e4636c1964d8c21049b990d1c5f90f0- donut shellcode:
40a6d7433ca98739f07fd0658cf3ecdd4c8119d601308122fc78dce8a2e47812 Mvgnd.dll:380b32e1c665993dcc4691bc1161ae5c44f94e7aeefe31adcd9acd83bc8313c2- C2 server :
151.242.170[.]228