<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>auth.ink blog</title>
  <link href="https://auth.ink/blog/"/>
  <link rel="self" href="https://auth.ink/blog/feed.xml"/>
  <id>https://auth.ink/blog/</id>
  <updated>2026-11-14T00:00:00Z</updated>
  <author><name>auth.ink</name></author>
  <entry>
    <title>Reversing SeroXen</title>
    <link href="https://auth.ink/blog/reversing-seroxen/"/>
    <id>https://auth.ink/blog/reversing-seroxen/</id>
    <published>2026-11-14T00:00:00Z</published>
    <updated>2026-11-14T00:00:00Z</updated>
    <summary>Static analysis of a SeroXen sample: the loader chain, the Quasar client and its C2 protocol, registry persistence, the modified r77 rootkit and Windows reset handling. IOCs and removal steps at the end.</summary>
    <content type="html">&lt;p&gt;SeroXen is a Windows RAT built from a modified Quasar client and a customized copy of bytecode77&amp;#39;s &lt;a href=&quot;https://github.com/bytecode77/r77-rootkit&quot;&gt;r77 user-mode rootkit&lt;/a&gt;. This post follows one sample from the obfuscated batch loader down to the rootkit DLL it injects into other processes.&lt;/p&gt;
&lt;p&gt;The sample is &lt;code&gt;sero.bat&lt;/code&gt;, a 13,039,164-byte batch file from &lt;a href=&quot;https://bazaar.abuse.ch/sample/4c6e90e178396d000b5dd5c5bb2b9ae5bbbca5986f26ffad2a6bd0845b6b2c83/&quot;&gt;MalwareBazaar&lt;/a&gt;, SHA-256 &lt;code&gt;4c6e90e178396d000b5dd5c5bb2b9ae5bbbca5986f26ffad2a6bd0845b6b2c83&lt;/code&gt;, first seen there on 3 October 2023. The Quasar payload, the SeroXen pipe names, the &lt;code&gt;$sxr&lt;/code&gt; artifacts and the native components all match the SeroXen build &lt;a href=&quot;https://www.levelblue.com/blogs/spiderlabs-blog/seroxen-rat-for-sale&quot;&gt;Alien Labs described earlier&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I used ILSpy CLI for the .NET assemblies and IDA Home with Hex-Rays for the native code, plus Python for batch expansion and resource decoding (dnfile for metadata, dncil for CIL, Cryptodome for the crypto). Everything here is static. I never ran the sample or anything extracted from it, so &amp;quot;the client does X&amp;quot; below always means &amp;quot;the code does X&amp;quot;. The C# excerpt is shortened and uses my own variable names. Disassembly keeps the original addresses.&lt;/p&gt;
&lt;h2 id=&quot;unpacking&quot;&gt;Unpacking&lt;/h2&gt;
&lt;p&gt;Most of the batch file is variable assignments and string substitutions that build a command at runtime. Instead of letting &lt;code&gt;cmd.exe&lt;/code&gt; run it, I wrote a Python parser that tracks the assignments, expands known variables and resolves the substring and replace operations as text. That turned up a PowerShell loader on line 12,140.&lt;/p&gt;
&lt;p&gt;The loader reads line 12,154 of the same file: the marker &lt;code&gt;SEROXEN&lt;/code&gt; followed by two encoded blobs separated by a backslash. Each blob is Base64, then AES-CBC with PKCS#7 padding, then GZip. In Python:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;ciphertext = base64.b64decode(blob, validate=True)
compressed = unpad(AES.new(key, AES.MODE_CBC, iv).decrypt(ciphertext), 16)
assembly = gzip.decompress(compressed)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both blobs decode to .NET PE files, a wrapper around the installer and an auxiliary assembly. The loader would reflection-load them and call their entry points. I wrote them to disk and opened them in ILSpy instead.&lt;/p&gt;
&lt;p&gt;The wrappers add another layer. Their encrypted settings hold the parameters for an embedded &lt;code&gt;payload.exe&lt;/code&gt; resource, and the settings themselves are AES-ECB with a 32-byte key built from an MD5 digest. The key construction is odd. The digest is copied into a zeroed 32-byte array at offset 0 and again at offset 15, so you end up with the first 15 digest bytes, then the full digest, then one zero byte. Build the key any other way and the settings won&amp;#39;t decrypt. The decoded settings give the AES-CBC parameters for the payload resource, which is also GZip-compressed.&lt;/p&gt;
&lt;p&gt;Unwrapping the first blob gave me the installer, &lt;code&gt;CSStub2&lt;/code&gt;. Its embedded payload is a second wrapper using the same scheme, and that one decodes to &lt;code&gt;Quasar.Client&lt;/code&gt;, an 8,143,360-byte assembly. I hashed every decoded output so I could match it against resources and registry payloads later.&lt;/p&gt;
&lt;p&gt;ILSpy decompiles the client, but the output needs cleanup. Many constants are built one character at a time, and control flow is split into small methods called through function-pointer tables. I wrote a decoder for the character arrays that only accepts complete, sequential literal assignments and swaps them for the readable string. For the more fragmented code I kept track of methods by MethodDef token and RVA and read the CIL with dncil.&lt;/p&gt;
&lt;p&gt;Most of the decompiled code isn&amp;#39;t SeroXen&amp;#39;s anyway. A Costura resolver loads Deflate-compressed copies of protobuf-net, Quasar.Common, the Task Scheduler library, MouseKeyHook, Ionic.Zip, SharpDX and BouncyCastle. I followed references from the client into those libraries to see which features it actually calls.&lt;/p&gt;
&lt;p&gt;The wrappers have VM checks over WMI and debugger checks through &lt;code&gt;Debugger.IsAttached&lt;/code&gt;, &lt;code&gt;CheckRemoteDebuggerPresent&lt;/code&gt; and &lt;code&gt;IsDebuggerPresent&lt;/code&gt;. The decoded &lt;code&gt;anti_vm&lt;/code&gt; setting is &lt;code&gt;False&lt;/code&gt; in this sample.&lt;/p&gt;
&lt;h2 id=&quot;config-and-c2-protocol&quot;&gt;Config and C2 protocol&lt;/h2&gt;
&lt;p&gt;I found the client settings by working back from the values passed to the connection manager and the &lt;code&gt;X509Certificate2&lt;/code&gt; object. They come from a class initialized by MethodDef &lt;code&gt;0x06000B90&lt;/code&gt;. Most of its strings are encrypted, but they all go through one decoder, so I could decrypt the whole config without touching the rest of the client.&lt;/p&gt;
&lt;p&gt;This decoder is different from the wrappers&amp;#39;. &lt;code&gt;Rfc2898DeriveBytes&lt;/code&gt; gets a master-key string, a fixed 32-byte salt and 50,000 iterations, and with that constructor it&amp;#39;s PBKDF2-HMAC-SHA1. Two reads in a row give the AES and HMAC keys:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;using var derivation = new Rfc2898DeriveBytes(masterKey, salt, 50000);
byte[] aesKey = derivation.GetBytes(32);
byte[] macKey = derivation.GetBytes(64);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The second &lt;code&gt;GetBytes&lt;/code&gt; continues the same stream. Create a fresh PBKDF2 instance for the HMAC key and you get the start of the stream again, which is the wrong key. Each stored value is a 32-byte HMAC-SHA256 tag, a 16-byte IV and AES-256-CBC ciphertext. The HMAC covers the IV and ciphertext and is checked before decryption and PKCS#7 unpadding.&lt;/p&gt;
&lt;p&gt;All ten settings passed the HMAC check in my decoder:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Connection string: &lt;code&gt;igboat[.]com:1167;&lt;/code&gt; (defanged)&lt;/li&gt;
&lt;li&gt;Identification string: &lt;code&gt;v2.2.2 | NuGeta&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Log directory name: &lt;code&gt;$sxr-Logs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Mutex: &lt;code&gt;Local\6a5dd02b-c4c3-4c95-8718-d851c4c1b042&lt;/code&gt;, which the single-instance helper creates and checks was newly created&lt;/li&gt;
&lt;li&gt;Also &lt;code&gt;1.0.0.0&lt;/code&gt;, &lt;code&gt;.exe&lt;/code&gt; and two empty installation/path fields&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The two big settings are an RSA signature and an X.509 certificate with a 4096-bit RSA public key. The master-key string is the uppercase SHA-1 fingerprint of that certificate, and I verified the signature offline over the SHA-256 hash of the master-key string. The HMACs prove I got the layout and key derivation right. The fingerprint and signature tie the certificate to the client&amp;#39;s startup checks.&lt;/p&gt;
&lt;p&gt;That certificate secures the C2 connection. MethodDef &lt;code&gt;0x0600043C&lt;/code&gt; (RVA &lt;code&gt;0x3B8C4&lt;/code&gt;) opens a TCP socket, turns on keepalive and wraps it in a &lt;code&gt;NetworkStream&lt;/code&gt; and an &lt;code&gt;SslStream&lt;/code&gt;. The TLS arguments are right there in the CIL:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cil&quot;&gt;IL_0057:  02           ldarg.0
IL_0058:  7b5e010004   ldfld        0x0400015e  // field token
IL_005D:  03           ldarg.1
IL_005E:  6f9000000a   callvirt     System.Object::ToString
IL_0063:  14           ldnull
IL_0064:  20000c0000   ldc.i4       3072
IL_0069:  16           ldc.i4.0
IL_006A:  6fe001000a   callvirt     System.Net.Security.SslStream::AuthenticateAsClient
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After the stream object come the target host string, a null client-certificate collection, &lt;code&gt;3072&lt;/code&gt; (TLS 1.2) and zero (no revocation check). The validation callback compares the server&amp;#39;s certificate with the embedded one using &lt;code&gt;Equals&lt;/code&gt;, so the client only accepts a server presenting that exact pinned certificate.&lt;/p&gt;
&lt;p&gt;The client does its own framing on top of TLS. Each message starts with a four-byte length read with &lt;code&gt;BitConverter.ToInt32&lt;/code&gt;. The receive worker keeps state between reads, so it can hold a partial header or payload and pick up a second message that arrived in the same read. Lengths of zero or less, or above &lt;code&gt;104857600&lt;/code&gt;, are rejected. If you write a parser for this traffic, don&amp;#39;t treat a TLS read as a message boundary.&lt;/p&gt;
&lt;p&gt;Messages implement &lt;code&gt;IMessage&lt;/code&gt; and are registered with protobuf-net through &lt;code&gt;TypeRegistry.AddTypesToSerializer&lt;/code&gt;. On connect, the client sends &lt;code&gt;ClientIdentification&lt;/code&gt; with the version, tag, OS description, account type, country information, hardware ID, username, computer name, an encryption-key field and the embedded signature. The version and tag come from splitting &lt;code&gt;v2.2.2 | NuGeta&lt;/code&gt; on the separator.&lt;/p&gt;
&lt;p&gt;Nothing reaches &lt;code&gt;MessageHandler.Process&lt;/code&gt; until the server answers with a &lt;code&gt;ClientIdentificationResult&lt;/code&gt; that marks the client as identified. After that, packets go to processors that check the type and call the matching handler. When the connection fails, the client retries after a configured 3,000 ms plus 250 to 749 ms of jitter from &lt;code&gt;Random.Next(250, 750)&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;remote-commands&quot;&gt;Remote commands&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;GetPasswords&lt;/code&gt; shows how a request from the operator turns into local data access. The handler at MethodDef &lt;code&gt;0x060004DF&lt;/code&gt; (RVA &lt;code&gt;0x43C50&lt;/code&gt;) builds ten recovery providers and loops over them. This is the collection step, with the metadata tokens left in for the obfuscated provider type and the generic list method:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cil&quot;&gt;IL_0063:  07           ldloc.1
IL_0064:  08           ldloc.2
IL_0065:  9a           ldelem.ref
IL_0066:  0d           stloc.3
IL_0067:  06           ldloc.0
IL_0068:  09           ldloc.3
IL_0069:  6f68030006   callvirt     0x06000368  // ReadAccounts
IL_006E:  6f3a02000a   callvirt     0x0A00023A  // List&amp;lt;T&amp;gt;.AddRange
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ldloc.0&lt;/code&gt; puts the result list on the stack and the provider goes on top. &lt;code&gt;ReadAccounts&lt;/code&gt; returns whatever that provider found, and &lt;code&gt;AddRange&lt;/code&gt; appends it to the list. Each provider call has its own exception handler, so one broken store doesn&amp;#39;t stop the rest. The final list goes into &lt;code&gt;GetPasswordsResponse.RecoveredAccounts&lt;/code&gt; and back to the server.&lt;/p&gt;
&lt;p&gt;The providers are Brave, Chrome, Edge, Firefox, Internet Explorer, Opera GX, Opera, Yandex, FileZilla and WinSCP. The Chromium ones share code but use different profile paths. They read &lt;code&gt;origin_url&lt;/code&gt;, &lt;code&gt;username_value&lt;/code&gt; and &lt;code&gt;password_value&lt;/code&gt; from the &lt;code&gt;logins&lt;/code&gt; table in &lt;code&gt;Login Data&lt;/code&gt;, then take &lt;code&gt;encrypted_key&lt;/code&gt; from &lt;code&gt;Local State&lt;/code&gt;, Base64-decode it, strip the five-byte prefix and unwrap it with &lt;code&gt;ProtectedData.Unprotect&lt;/code&gt; under &lt;code&gt;DataProtectionScope.CurrentUser&lt;/code&gt;. Passwords starting with &lt;code&gt;v10&lt;/code&gt; are decrypted with that key using AES-GCM. Anything else goes straight to DPAPI, also as &lt;code&gt;CurrentUser&lt;/code&gt;, so recovery depends on running as the user who owns the profile. These are the formats this sample handles. I didn&amp;#39;t check it against newer browser protections.&lt;/p&gt;
&lt;p&gt;Firefox recovery looks for &lt;code&gt;signons.sqlite&lt;/code&gt; and &lt;code&gt;logins.json&lt;/code&gt; and resolves NSS functions including &lt;code&gt;NSS_Init&lt;/code&gt; and &lt;code&gt;PK11SDR_Decrypt&lt;/code&gt;. FileZilla recovery pulls Host, Port, User and Pass from &lt;code&gt;recentservers.xml&lt;/code&gt; and &lt;code&gt;sitemanager.xml&lt;/code&gt; and Base64-decodes the password. WinSCP recovery reads sessions under the current user&amp;#39;s &lt;code&gt;Martin Prikryl\WinSCP 2\Sessions&lt;/code&gt; registry key and runs its own decoding routine on them.&lt;/p&gt;
&lt;p&gt;The other commands go through the same dispatch. &lt;code&gt;DoShellExecute&lt;/code&gt; creates or reuses a shell object, passes it &lt;code&gt;message.Command&lt;/code&gt; and disposes it on &lt;code&gt;exit&lt;/code&gt;. The file manager lists drives and directories and handles transfers in chunks, plus cancel, delete and rename. Process and registry handlers list and modify what you&amp;#39;d expect. The reverse proxy opens sockets to the requested destinations and tags relayed data with connection IDs, so several proxied connections can be kept apart from each other and from the client&amp;#39;s own control connection.&lt;/p&gt;
&lt;p&gt;The desktop handler takes capture, monitor, mouse and keyboard messages. Separate code creates a &lt;code&gt;VirtualDesktop&lt;/code&gt; with &lt;code&gt;CreateDesktopW&lt;/code&gt; and captures bitmaps from it. Keyboard-hook code and keylog-directory retrieval are in there too, though I didn&amp;#39;t work out which features are switched on at startup. Some packet names are misleading leftovers. &lt;code&gt;DoWebcamStop&lt;/code&gt; shows up in unrelated dispatch paths, so I didn&amp;#39;t count it as evidence of webcam capture. &lt;code&gt;DoAskElevate&lt;/code&gt; is clearer: it uses the &lt;code&gt;runas&lt;/code&gt; verb and reports &amp;quot;User refused the elevation request&amp;quot; if that fails, so elevation goes through a normal UAC prompt.&lt;/p&gt;
&lt;h2 id=&quot;persistence&quot;&gt;Persistence&lt;/h2&gt;
&lt;p&gt;After installation SeroXen doesn&amp;#39;t need the batch file anymore. To find what replaces it, I followed the registry writes and task registration in &lt;code&gt;CSStub2&lt;/code&gt;. One string, &lt;code&gt;$sxr-ArapbJcqiFeDiSwKsZvE&lt;/code&gt;, ties together the payload value, the environment variables and the scheduled task.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ConstructorReturnMessageEmitCall&lt;/code&gt; (MethodDef &lt;code&gt;0x06000303&lt;/code&gt;, RVA &lt;code&gt;0x81EAC&lt;/code&gt;) calls &lt;code&gt;Registry.LocalMachine.OpenSubKey(&amp;quot;SOFTWARE&amp;quot;, writable: true)&lt;/code&gt; and writes a &lt;code&gt;REG_SZ&lt;/code&gt; value with that name. The value sits directly under &lt;code&gt;SOFTWARE&lt;/code&gt;. If you go looking for a subkey you&amp;#39;ll miss it. The data is 7,750,189 ASCII characters: two encrypted Base64 blobs separated by a backslash. They decode to the RAT-loader wrapper and the same auxiliary assembly I pulled out of the batch file, byte for byte, which is how I linked the installed launch path to the original one.&lt;/p&gt;
&lt;p&gt;The installer also copies three Windows binaries into the Windows directory as &lt;code&gt;$sxr-mshta.exe&lt;/code&gt;, &lt;code&gt;$sxr-cmd.exe&lt;/code&gt; and &lt;code&gt;$sxr-powershell.exe&lt;/code&gt;, and writes three machine-scoped environment variables named with the same stem plus a suffix. Machine scope means they outlive the installer process.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;4321&lt;/code&gt; holds the path of the copied mshta.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;4312&lt;/code&gt; holds the obfuscated launch command that the task uses.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;1234&lt;/code&gt; is a working copy used while the launcher is prepared. &lt;code&gt;StartDTDSubsetGetTypeFromProgID&lt;/code&gt; decrypts the command, inserts the same generated 20-character string at every zero-length match of the pattern &lt;code&gt;.{0}&lt;/code&gt; and saves that copy to the machine environment. &lt;code&gt;CdfIdentityDictionaryKeyCollection&lt;/code&gt; reads it back and strips the inserted string and the PowerShell executable text.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When the task fires, its executable field expands &lt;code&gt;4321&lt;/code&gt;, which starts the copied mshta. The mshta arguments contain script that calls &lt;code&gt;WScript.Shell.Run&lt;/code&gt; to start the copied cmd. Cmd expands &lt;code&gt;4312&lt;/code&gt;, strips the inserted &lt;code&gt;&amp;amp;#&amp;lt;?&lt;/code&gt; marker and starts the copied PowerShell. PowerShell reads the registry value, splits the two blobs, decrypts and decompresses them and calls their entry points:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;scheduled task
  -&amp;gt; $sxr-mshta.exe -&amp;gt; $sxr-cmd.exe -&amp;gt; $sxr-powershell.exe
  -&amp;gt; HKLM\SOFTWARE payload value
  -&amp;gt; auxiliary assembly + RAT loader -&amp;gt; Quasar.Client
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Deleting the original batch file does nothing here. The code is in the registry, the launch command is in the environment and the hosts are in the Windows directory. The RAT gets rebuilt locally every time, without downloading anything from C2.&lt;/p&gt;
&lt;p&gt;The task is built by MethodDef &lt;code&gt;0x06000251&lt;/code&gt; (RVA &lt;code&gt;0x719F0&lt;/code&gt;). It has a &lt;code&gt;LogonTrigger&lt;/code&gt; with no user filter and a &lt;code&gt;TimeTrigger&lt;/code&gt; starting on &lt;code&gt;1999-01-01&lt;/code&gt; that repeats every five minutes. Run level is Highest, with no execution time limit and no idle or network condition, and it runs on battery. &lt;code&gt;MultipleInstances&lt;/code&gt; is &lt;code&gt;IgnoreNew&lt;/code&gt; and &lt;code&gt;Hidden&lt;/code&gt; is false. The decoded PowerShell command also checks for an already running &lt;code&gt;$sxr-powershell&lt;/code&gt; process.&lt;/p&gt;
&lt;p&gt;Another helper runs the task, sets the principal group to &lt;code&gt;BUILTIN\Users&lt;/code&gt; and registers it again. That&amp;#39;s what brings the RAT back after logon. There&amp;#39;s no &lt;code&gt;BootTrigger&lt;/code&gt;, and the task isn&amp;#39;t assigned to SYSTEM. The install itself needs enough privilege to write to the Windows directory and HKLM, and Highest run level won&amp;#39;t give admin rights to an account that doesn&amp;#39;t have them.&lt;/p&gt;
&lt;p&gt;The registry-plus-PowerShell setup comes from r77&amp;#39;s fileless install. The &lt;a href=&quot;https://github.com/bytecode77/r77-rootkit/blob/134320f841133c9adf15f78d64b3ed1c86042b05/Install/Install.c&quot;&gt;upstream installer&lt;/a&gt; stores a stager as a &lt;code&gt;REG_BINARY&lt;/code&gt; value under &lt;code&gt;HKLM\SOFTWARE&lt;/code&gt; and creates a task that loads it through PowerShell reflection. SeroXen stores encrypted text in a &lt;code&gt;REG_SZ&lt;/code&gt; instead, and its task reaches PowerShell through the copied mshta and cmd and the environment variables.&lt;/p&gt;
&lt;h2 id=&quot;the-r77-layer&quot;&gt;The r77 layer&lt;/h2&gt;
&lt;p&gt;The native side is mostly r77. The process-hollowing host, PID registration, DLL unhooking, critical-process check and enumeration hooks all match. The hiding prefix changed from &lt;code&gt;$77&lt;/code&gt; to &lt;code&gt;$sxr&lt;/code&gt;, so &lt;code&gt;$77config&lt;/code&gt; became &lt;code&gt;$sxrconfig&lt;/code&gt;. SeroXen&amp;#39;s own work is the packaging and the reset interception. I compared against r77&amp;#39;s September 2023 &lt;a href=&quot;https://github.com/bytecode77/r77-rootkit/blob/134320f841133c9adf15f78d64b3ed1c86042b05/Service/Service.c&quot;&gt;native host&lt;/a&gt; and &lt;a href=&quot;https://github.com/bytecode77/r77-rootkit/blob/134320f841133c9adf15f78d64b3ed1c86042b05/r77/Hooks.c&quot;&gt;hooks&lt;/a&gt;, but I haven&amp;#39;t pinned this binary to an exact upstream version.&lt;/p&gt;
&lt;p&gt;Besides the network dispatcher, the client has a local named pipe. Its name is the &lt;code&gt;$sxr&lt;/code&gt; prefix, then &lt;code&gt;-SeroXenClientNamedPipe_&lt;/code&gt;, then a fixed suffix (the full name is in the IOC table). Matching the installer&amp;#39;s and client&amp;#39;s pipe operations leads to a handler that loads &lt;code&gt;Quasar.Client.InstallStager.exe&lt;/code&gt;. That assembly carries 32-bit and 64-bit native payloads and the code that puts them inside &lt;code&gt;dllhost.exe&lt;/code&gt;, taken from &lt;code&gt;System32&lt;/code&gt; or &lt;code&gt;SysWOW64&lt;/code&gt; depending on architecture.&lt;/p&gt;
&lt;p&gt;On x64, InstallStager starts the host with &lt;code&gt;CreateProcessInternal&lt;/code&gt;, reads the thread context, unmaps the original image with &lt;code&gt;NtUnmapViewOfSection&lt;/code&gt; and allocates room for the replacement with &lt;code&gt;VirtualAllocEx&lt;/code&gt;. It writes the PE headers and sections with &lt;code&gt;WriteProcessMemory&lt;/code&gt;, updates the remote image-base pointer, puts the new entry point in &lt;code&gt;RCX&lt;/code&gt; (&lt;code&gt;EAX&lt;/code&gt; on x86) with &lt;code&gt;SetThreadContext&lt;/code&gt; and resumes the thread. It&amp;#39;s textbook process hollowing. The process is &lt;code&gt;dllhost.exe&lt;/code&gt; on disk, but the image in its memory is the native host.&lt;/p&gt;
&lt;p&gt;The 64-bit host&amp;#39;s main routine at &lt;code&gt;0x140001B30&lt;/code&gt; tries to enable &lt;code&gt;SeDebugPrivilege&lt;/code&gt;, loads the DLL embedded as resource type &lt;code&gt;DLL&lt;/code&gt;, ID &lt;code&gt;101&lt;/code&gt;, and starts its injection threads. It writes its PID as &lt;code&gt;svc64&lt;/code&gt; under &lt;code&gt;HKLM\SOFTWARE\$sxrconfig\pid&lt;/code&gt;. The name comes from r77, which reserves &lt;code&gt;svc32&lt;/code&gt; and &lt;code&gt;svc64&lt;/code&gt; for its host PIDs. No SCM service is involved. Registering the PID lets the rootkit hide the host even though &lt;code&gt;dllhost.exe&lt;/code&gt; doesn&amp;#39;t carry the &lt;code&gt;$sxr&lt;/code&gt; prefix.&lt;/p&gt;
&lt;p&gt;Before its main loop, &lt;code&gt;sub_140001908&lt;/code&gt; maps &lt;code&gt;ntdll.dll&lt;/code&gt; and &lt;code&gt;kernel32.dll&lt;/code&gt; from &lt;code&gt;C:\Windows\System32&lt;/code&gt;, finds their &lt;code&gt;.text&lt;/code&gt; sections, makes the loaded copies writable with &lt;code&gt;VirtualProtect&lt;/code&gt;, copies the clean code over them and restores the old protection. That undoes any patches in the loaded copies, including hooks a security product may have placed there. The injection routine at &lt;code&gt;0x140001000&lt;/code&gt; opens eligible processes, skips critical ones by checking &lt;code&gt;ProcessBreakOnTermination&lt;/code&gt;, allocates executable memory in the target, writes the embedded DLL and calls a helper to start it. &lt;a href=&quot;https://github.com/bytecode77/r77-rootkit/blob/134320f841133c9adf15f78d64b3ed1c86042b05/r77api/r77process.c&quot;&gt;r77&amp;#39;s injection code&lt;/a&gt; has the same critical-process check.&lt;/p&gt;
&lt;p&gt;The injected DLL hooks the APIs other programs use to list things. The registration routine at &lt;code&gt;0x180001EB0&lt;/code&gt; hooks &lt;code&gt;NtQuerySystemInformation&lt;/code&gt;, the directory-query APIs, registry and service enumeration, &lt;code&gt;NtResumeThread&lt;/code&gt; and &lt;code&gt;NtDeviceIoControlFile&lt;/code&gt;. For system-information class 5 (the process list), &lt;code&gt;sub_18000221C&lt;/code&gt; calls the original function and then walks the records it got back. The prefix check looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;00000001800022B2  41b804000000     mov     r8d, 4; nChar
00000001800022B8  488d1581890100   lea     rdx, aSxr; &amp;quot;$sxr&amp;quot;
00000001800022BF  ff153b100100     call    cs:StrCmpNIW; SHLWAPI
00000001800022C5  4533db           xor     r11d, r11d
00000001800022C8  85c0             test    eax, eax
00000001800022CA  742c             jz      short loc_1800022F8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;RCX&lt;/code&gt; already holds the process name. &lt;code&gt;RDX&lt;/code&gt; gets &lt;code&gt;$sxr&lt;/code&gt; and &lt;code&gt;R8D&lt;/code&gt; gets 4, so this is a case-insensitive compare of the first four characters. A match returns zero and jumps to the exclusion path at &lt;code&gt;0x1800022F8&lt;/code&gt;. Elsewhere the function also checks PID and name filters.&lt;/p&gt;
&lt;p&gt;Hiding a record means rewriting the links in the returned buffer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;0000000180002304  418b06           mov     eax, [r14]
0000000180002307  4d85ff           test    r15, r15
000000018000230A  740e             jz      short loc_18000231A
000000018000230C  85c0             test    eax, eax
000000018000230E  7405             jz      short loc_180002315
0000000180002310  410107           add     [r15], eax
0000000180002313  eb11             jmp     short loc_180002326
0000000180002315  45891f           mov     [r15], r11d
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;R14&lt;/code&gt; is the current record and &lt;code&gt;R15&lt;/code&gt; is the last record that was kept. The first DWORD of each record is &lt;code&gt;NextEntryOffset&lt;/code&gt;, the byte distance to the next one. At &lt;code&gt;0x180002310&lt;/code&gt; the current record&amp;#39;s offset gets added to the previous record&amp;#39;s. Say the previous offset was &lt;code&gt;0x100&lt;/code&gt; and the hidden record&amp;#39;s was &lt;code&gt;0x180&lt;/code&gt;: the previous one becomes &lt;code&gt;0x280&lt;/code&gt;, and anything walking the list jumps straight over the hidden record. If the hidden record was the last one, the previous offset is set to zero.&lt;/p&gt;
&lt;p&gt;The process keeps running. Only the list handed to the caller changes. The function also adds up the accounting fields of every hidden entry and adds the totals to the record with PID 0, so the idle process absorbs the hidden processes&amp;#39; numbers.&lt;/p&gt;
&lt;p&gt;The registry filters at &lt;code&gt;0x180002A08&lt;/code&gt; and &lt;code&gt;0x180002AF4&lt;/code&gt; do the same for registry enumeration and depend on internal synchronization state. A registry value can exist and still be missing from what a hooked tool shows you. That&amp;#39;s the point of all the &lt;code&gt;$sxr&lt;/code&gt; names in the installer.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;NtResumeThread&lt;/code&gt; hook at &lt;code&gt;0x180002444&lt;/code&gt; spreads the injection to new processes. It gets the target PID with &lt;code&gt;GetProcessIdOfThread&lt;/code&gt;, skips its own process and checks the target&amp;#39;s bitness. Then it opens the 32-bit or 64-bit &lt;code&gt;$sxrchildproc&lt;/code&gt; pipe, writes the PID as four bytes and waits for a one-byte reply. On the other end, the host&amp;#39;s listener at &lt;code&gt;0x140001F94&lt;/code&gt; runs its injection callback and replies with byte &lt;code&gt;77&lt;/code&gt;. Only then does the hook call the real &lt;code&gt;NtResumeThread&lt;/code&gt;, so the injection is attempted while the child&amp;#39;s first thread is still suspended. The reply just means the callback finished. It doesn&amp;#39;t confirm the injection worked, and if the pipe can&amp;#39;t be opened the hook resumes the thread as normal.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;NtDeviceIoControlFile&lt;/code&gt; hook at &lt;code&gt;0x180002D78&lt;/code&gt; handles network listings. After calling the original, it looks for a successful &lt;code&gt;0x12001B&lt;/code&gt; request with a 112-byte output structure on a handle whose object name is &lt;code&gt;\Device\Nsi&lt;/code&gt;. For TCP and UDP tables it checks the port filters and process-hiding rules, shifts the following connection, status and process entries over each hidden row and decrements the count. netstat and similar tools that go through this path won&amp;#39;t show the connection. The socket stays open, and the traffic still shows up in a network capture.&lt;/p&gt;
&lt;p&gt;Next to the r77 hooks, this build has one that targets reset. It loads &lt;code&gt;spopk.dll&lt;/code&gt; and replaces &lt;code&gt;Sysprep_Clean_Validate_Opk&lt;/code&gt; with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;0000000180002218  33c0             xor     eax, eax
000000018000221A  c3               retn
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It returns zero immediately, so the real validator never runs. I didn&amp;#39;t test whether the hook actually lands in a given Sysprep process. In Remove mode (below), ResetPatcher runs the real Sysprep after its cleanup.&lt;/p&gt;
&lt;h2 id=&quot;reset-persistence&quot;&gt;Reset persistence&lt;/h2&gt;
&lt;p&gt;SeroXen is advertised as surviving a Windows reset. In this sample the reset never actually runs. The client kills it as it starts and puts up its own reset screen instead, with no command from the operator needed. It sets up a &lt;code&gt;ManagementEventWatcher&lt;/code&gt; on &lt;code&gt;SELECT * FROM Win32_ProcessStartTrace&lt;/code&gt;. For each new process it gets the PID, resolves the process object and compares &lt;code&gt;MainModule.FileName&lt;/code&gt; with &lt;code&gt;Windows\System32\SystemSettingsAdminFlows.exe&lt;/code&gt; on the system drive. If that matches, it checks for the operation argument &lt;code&gt;FeaturedResetPC&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Those two checks catch Settings starting a reset. The client calls &lt;code&gt;proc.Kill()&lt;/code&gt; on it and loads the embedded &lt;code&gt;Quasar.Client.ResetPatcher.bin&lt;/code&gt;, passing &lt;code&gt;FeaturedResetPC&lt;/code&gt;, the sample identifier and an internal authentication argument to its entry point. The resource is AES-ECB with an MD5-derived key, PKCS#7 padding and raw Deflate, and decodes to a 1,769,472-byte WinForms assembly.&lt;/p&gt;
&lt;p&gt;The assembly is a WinForms copy of the reset dialog. It&amp;#39;s titled &amp;quot;Reset this PC&amp;quot; and has resources for both &amp;quot;Keep my files&amp;quot; and &amp;quot;Remove everything&amp;quot;. Once the user continues, the form hides its options, goes borderless, maximized and topmost, and loads the embedded &lt;code&gt;SystemSettingsAdminFlows.ResetHTML.html&lt;/code&gt; into a &lt;code&gt;WebBrowser&lt;/code&gt; control. Input blocking and a hidden cursor keep the user from doing anything while it&amp;#39;s up.&lt;/p&gt;
&lt;p&gt;The progress on screen isn&amp;#39;t connected to anything. A &lt;code&gt;setInterval(timer, 14830)&lt;/code&gt; in the page moves the percentage, and the managed worker separately waits on &lt;code&gt;Task.Delay(1400000)&lt;/code&gt;, which is 23 minutes 20 seconds. Neither one reads anything from a real recovery process, so the number keeps climbing while deletions fail, files stay locked or account operations get skipped.&lt;/p&gt;
&lt;h3 id=&quot;cleanup-in-both-modes&quot;&gt;Cleanup in both modes&lt;/h3&gt;
&lt;p&gt;The cleanup worker lists the immediate subdirectories of the shared Start-menu &lt;code&gt;Programs&lt;/code&gt; folder, Program Files, Program Files (x86) and ProgramData. It lowercases each name, checks it against an exception list and recursively deletes everything that isn&amp;#39;t on it. No uninstallers run, so the apps&amp;#39; registry entries and services can stay behind. Loose files sitting directly in those folders survive too, since only directories are listed.&lt;/p&gt;
&lt;p&gt;The exception lists (in full at the end) keep a lot of Windows and Microsoft folders. The Start-menu one has a bug. It includes names like &lt;code&gt;Microsoft Edge.lnk&lt;/code&gt;, but the loop calls &lt;code&gt;Directory.GetDirectories&lt;/code&gt;, so a normal shortcut file can never match. Both modes also go after the current user&amp;#39;s &lt;code&gt;$sxr-Logs&lt;/code&gt; directory and the files under the Public Desktop.&lt;/p&gt;
&lt;p&gt;Account removal starts with a WinNT user enumeration. &lt;code&gt;Administrator&lt;/code&gt;, &lt;code&gt;DefaultAccount&lt;/code&gt;, &lt;code&gt;Guest&lt;/code&gt; and &lt;code&gt;WDAGUtilityAccount&lt;/code&gt; are exempt by exact name, and deletion commands for every other account are started asynchronously. After that the two modes split.&lt;/p&gt;
&lt;h3 id=&quot;keep-my-files&quot;&gt;Keep my files&lt;/h3&gt;
&lt;p&gt;The current &lt;code&gt;Environment.UserName&lt;/code&gt; is also exempt, and the all-profile deletion pass is skipped. The code tries to remove the current user&amp;#39;s roaming AppData and the Explorer &lt;code&gt;Taskband&lt;/code&gt; key, then sets up more cleanup under &lt;code&gt;HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce&lt;/code&gt; with the value name &lt;code&gt;$sxr-System_Reset_Task&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That command uses &lt;code&gt;$sxr-cmd.exe&lt;/code&gt; to start the real &lt;code&gt;FirstLogonAnim.exe&lt;/code&gt; with &lt;code&gt;/RunFirstLogonAnim /explorer&lt;/code&gt;, the animation Windows shows on a first logon. After roughly five pings of delay it tries to delete the &lt;code&gt;Taskband\Favorites&lt;/code&gt; registry path, removes the original profile&amp;#39;s whole AppData directory and kills and restarts Explorer. Roughly forty pings later it kills the animation. The profile path is captured when the command is built, but the &lt;code&gt;HKCU&lt;/code&gt; operations apply to whoever runs RunOnce, and that may be a different user.&lt;/p&gt;
&lt;p&gt;The Taskband command treats &lt;code&gt;Favorites&lt;/code&gt; as a key path, not a value selected with &lt;code&gt;/v&lt;/code&gt;. The file cleanup also doesn&amp;#39;t explicitly touch the current profile&amp;#39;s Documents, Pictures, Downloads or Desktop. Other non-exempt accounts are still targeted. Keep mode ends with a forced reboot and never calls Sysprep.&lt;/p&gt;
&lt;h3 id=&quot;remove-everything&quot;&gt;Remove everything&lt;/h3&gt;
&lt;p&gt;The shared account pass also targets the current user, unless the name matches one of the built-in exemptions. Terminal Services sessions other than the reset process&amp;#39;s own are logged off. The profile pass lists &lt;code&gt;%SystemDrive%\Users&lt;/code&gt;, skipping &lt;code&gt;Default&lt;/code&gt;, &lt;code&gt;Public&lt;/code&gt;, &lt;code&gt;All Users&lt;/code&gt; and &lt;code&gt;Default User&lt;/code&gt;. It also skips any folder whose lowercased name contains the original profile name, which can spare more folders than an exact match would.&lt;/p&gt;
&lt;p&gt;For each remaining folder it looks up the folder name with &lt;code&gt;UserPrincipal.FindByIdentity&lt;/code&gt;. Only if that returns an account does it call &lt;code&gt;UserPrincipal.Delete&lt;/code&gt; and recursively delete the folder. That races with the asynchronous account deletions from earlier. If the account is already gone, the lookup fails and the profile folder is skipped. So even before file locks and swallowed exceptions, it can&amp;#39;t be counted on to wipe every profile.&lt;/p&gt;
&lt;p&gt;Remove mode also tries to delete the executing user&amp;#39;s profile and writes its own version of &lt;code&gt;$sxr-System_Reset_Task&lt;/code&gt;. Its delayed targets are &lt;code&gt;Taskband\Favorites&lt;/code&gt;, &lt;code&gt;C:\Users\defaultuser0&lt;/code&gt; and the captured original profile, without the FirstLogonAnim and Explorer steps. Both RunOnce entries only clean up. The repeating scheduled task is still what relaunches the RAT.&lt;/p&gt;
&lt;p&gt;When the cleanup workers finish and the fixed delay runs out, Remove mode force-kills any running Sysprep through a hidden command shell, waits up to ten seconds for that and starts the real Sysprep with exactly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;%SystemRoot%\System32\sysprep\sysprep.exe /oobe /reboot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It sets &lt;code&gt;UseShellExecute = true&lt;/code&gt; and &lt;code&gt;WindowStyle = Hidden&lt;/code&gt;, then immediately suspends the new process with a helper that opens each thread with &lt;code&gt;SUSPEND_RESUME&lt;/code&gt; access and calls &lt;code&gt;SuspendThread&lt;/code&gt;. Ten seconds later it tries to resume Winlogon and then Sysprep. Separately, the UI/watch path suspends a Winlogon process it finds with &lt;code&gt;NtSuspendProcess&lt;/code&gt;, and a matching helper calls &lt;code&gt;NtResumeProcess&lt;/code&gt;. Winlogon is suspended and resumed, and its file on disk is left alone.&lt;/p&gt;
&lt;p&gt;According to &lt;a href=&quot;https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/sysprep-command-line-options?view=windows-11&quot;&gt;Microsoft&amp;#39;s Sysprep reference&lt;/a&gt;, &lt;code&gt;/oobe&lt;/code&gt; boots into first-run setup and &lt;code&gt;/reboot&lt;/code&gt; restarts the machine. There&amp;#39;s no &lt;code&gt;/generalize&lt;/code&gt; and no &lt;code&gt;/unattend&lt;/code&gt; answer file, and nothing asks for the image to be replaced or the disk formatted. The user can get account creation and the other first-run screens, on the same Windows install the malware just finished cleaning up.&lt;/p&gt;
&lt;p&gt;Two more resources, &lt;code&gt;ULLH_KeepFiles&lt;/code&gt; and &lt;code&gt;ULLH_RemoveAllFiles&lt;/code&gt;, hold related cleanup and reboot helpers. Their launchers can wait for a given PID to exit before loading the chosen assembly in memory. They handle input blocking, display suppression, Winlogon resumption and log removal, and finish with a forced reboot or Sysprep. I decoded both, but I&amp;#39;m not assuming they run on every reset attempt.&lt;/p&gt;
&lt;h3 id=&quot;what-the-feature-amounts-to&quot;&gt;What the feature amounts to&lt;/h3&gt;
&lt;p&gt;None of the persistence is in the cleanup paths above: not the task, the machine environment variables, the copied binaries in the Windows directory or the &lt;code&gt;HKLM\SOFTWARE&lt;/code&gt; payload. The next time someone logs on, the launcher has everything it needs to rebuild the RAT. So the advertised reset persistence works only because the real reset never happens, and only when it&amp;#39;s started from Settings while the watcher is running, sees the process and manages to kill it. I didn&amp;#39;t find anything in this sample that would carry it through a reset that actually completes, whether that&amp;#39;s started from Windows RE, done by reinstalling from external media or by wiping the partition.&lt;/p&gt;
&lt;h2 id=&quot;detection-and-removal&quot;&gt;Detection and removal&lt;/h2&gt;
&lt;p&gt;The best disk indicators are the linked install artifacts: the &lt;code&gt;$sxr-ArapbJcqiFeDiSwKsZvE&lt;/code&gt; task and &lt;code&gt;HKLM\SOFTWARE&lt;/code&gt; value, the three environment variables with the same stem and the renamed Windows binaries. Check the binaries together with the task action and the stored payload. On their own they&amp;#39;re real Windows executables and may carry valid Microsoft signatures. In memory, look for the replacement image inside &lt;code&gt;dllhost.exe&lt;/code&gt; and injected copies of the rootkit. Network-side logs will show traffic the local connection table hides.&lt;/p&gt;
&lt;p&gt;Don&amp;#39;t trust live listings while the rootkit is active. Its process, directory and registry hooks can hide all of this from tools running in injected processes, including task files and registry data read through those APIs. The task has &lt;code&gt;Hidden=false&lt;/code&gt;, and I didn&amp;#39;t confirm whether every Task Scheduler view misses it. Starting a fresh tool doesn&amp;#39;t guarantee a clean view either, because the child-process hook asks for injection before the new process&amp;#39;s first thread runs.&lt;/p&gt;
&lt;p&gt;Cleaning a machine means dealing with both the saved launcher and the DLLs already in memory:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Take the machine off the network and preserve disk and memory evidence before deleting anything.&lt;/li&gt;
&lt;li&gt;Inspect and clean from trusted external recovery media, or from an incident-response environment that can see the install without the injected user-mode hooks. Check both registry views, because the installer&amp;#39;s &lt;code&gt;HKLM\SOFTWARE&lt;/code&gt; access doesn&amp;#39;t pick an explicit view.&lt;/li&gt;
&lt;li&gt;Disable or remove the task with trusted remediation tooling. Remove the exact registry payload value, the three matching machine environment variables, the &lt;code&gt;$sxrconfig&lt;/code&gt; key, the &lt;code&gt;$sxr-System_Reset_Task&lt;/code&gt; RunOnce value if it&amp;#39;s there and the three &lt;code&gt;$sxr-&lt;/code&gt; binaries. Keep copies for analysis and remove only the artifacts you&amp;#39;ve identified.&lt;/li&gt;
&lt;li&gt;Reboot once the launch chain is gone, then check the artifacts and processes again with trusted tools after logon and across the five-minute task interval. Deleting the payload on a running system doesn&amp;#39;t unload DLLs already injected into other processes.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you need a known-clean system, save the data you need and reinstall Windows from trusted external media. The Settings reset won&amp;#39;t do it, since this client can intercept it while running. Reset passwords and revoke sessions from a separate clean device. Removing the RAT doesn&amp;#39;t undo credential theft that may have already happened.&lt;/p&gt;
&lt;h2 id=&quot;iocs&quot;&gt;IOCs&lt;/h2&gt;
&lt;p&gt;These were pulled from this sample and show its configuration when it was built. They say nothing about who owns or runs the domain today.&lt;/p&gt;
&lt;div class=&quot;table&quot;&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Configured endpoint&lt;/td&gt;
&lt;td&gt;&lt;code&gt;igboat[.]com:1167&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client version / tag&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v2.2.2&lt;/code&gt; / &lt;code&gt;NuGeta&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Certificate SHA-256&lt;/td&gt;
&lt;td&gt;&lt;code&gt;8ce86540ca5f8992c923341c10fd9d87089e74574ac3f6089d558c725fa78b8c&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Task&lt;/td&gt;
&lt;td&gt;&lt;code&gt;\$sxr-ArapbJcqiFeDiSwKsZvE&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Registry payload&lt;/td&gt;
&lt;td&gt;Value &lt;code&gt;$sxr-ArapbJcqiFeDiSwKsZvE&lt;/code&gt; directly under &lt;code&gt;HKLM\SOFTWARE&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Machine environment variables&lt;/td&gt;
&lt;td&gt;The same stem with suffixes &lt;code&gt;4321&lt;/code&gt;, &lt;code&gt;1234&lt;/code&gt;, &lt;code&gt;4312&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Launcher copies&lt;/td&gt;
&lt;td&gt;&lt;code&gt;%SystemRoot%\$sxr-mshta.exe&lt;/code&gt;, &lt;code&gt;$sxr-cmd.exe&lt;/code&gt;, &lt;code&gt;$sxr-powershell.exe&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native host PID&lt;/td&gt;
&lt;td&gt;&lt;code&gt;HKLM\SOFTWARE\$sxrconfig\pid&lt;/code&gt;, DWORD &lt;code&gt;svc64&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reset cleanup&lt;/td&gt;
&lt;td&gt;&lt;code&gt;HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce&lt;/code&gt;, value &lt;code&gt;$sxr-System_Reset_Task&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Log directory&lt;/td&gt;
&lt;td&gt;&lt;code&gt;%AppData%\$sxr-Logs&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Managed client pipe&lt;/td&gt;
&lt;td&gt;&lt;code&gt;\\.\pipe\$sxr-SeroXenClientNamedPipe_DfDSWJQXBSBVdJXIBeMKrhdiNahnVenOKDfDSWJQXBSBVdJXIBeMKyWKhobLMDFTgSLOHXDfDSWJQXBSBVdJXIBeMK&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Child-injection pipe, 32-bit target&lt;/td&gt;
&lt;td&gt;&lt;code&gt;\\.\pipe\$sxrchildproc34226543a32&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Child-injection pipe, 64-bit target&lt;/td&gt;
&lt;td&gt;&lt;code&gt;\\.\pipe\$sxrchildproc38764243a64&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Method references, component hashes and the cleanup exception lists are below if you want to check my work. Native addresses use each module&amp;#39;s preferred image base. I compared the printed instruction bytes against the original PE files, and checked the config decoding with all ten HMACs, the certificate fingerprint and an offline RSA signature check.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;Methods and addresses&lt;/summary&gt;&lt;div class=&quot;table&quot;&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Method or address&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Installer&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ConstructorReturnMessageEmitCall&lt;/code&gt;, &lt;code&gt;0x06000303&lt;/code&gt;, RVA &lt;code&gt;0x81EAC&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Registry payload write&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Installer&lt;/td&gt;
&lt;td&gt;&lt;code&gt;getAccessControlTypePercentGroupSeparator&lt;/code&gt;, &lt;code&gt;0x06000251&lt;/code&gt;, RVA &lt;code&gt;0x719F0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Scheduled-task construction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Installer&lt;/td&gt;
&lt;td&gt;&lt;code&gt;InheritedObjectAceTypePresentIndexOfValue&lt;/code&gt;, &lt;code&gt;0x060000C0&lt;/code&gt;, RVA &lt;code&gt;0x5AB70&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run task; update principal group&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quasar client&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ThumbprintDoCallBack&lt;/code&gt;, &lt;code&gt;0x06000239&lt;/code&gt;, RVA &lt;code&gt;0x2A8D8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Settings executable-path check&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quasar client&lt;/td&gt;
&lt;td&gt;&lt;code&gt;setPersistHandlerOffset&lt;/code&gt;, &lt;code&gt;0x06000238&lt;/code&gt;, RVA &lt;code&gt;0x2A6F0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;FeaturedResetPC&lt;/code&gt; argument check&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quasar client&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DisablePrivateReflectionAttributeComponent&lt;/code&gt;, &lt;code&gt;0x06000245&lt;/code&gt;, RVA &lt;code&gt;0x2C190&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Kill intercepted process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quasar client&lt;/td&gt;
&lt;td&gt;&lt;code&gt;TaskExecuteSchemaVersion&lt;/code&gt;, &lt;code&gt;0x0600023C&lt;/code&gt;, RVA &lt;code&gt;0x2AFC8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Recover and load ResetPatcher&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quasar client&lt;/td&gt;
&lt;td&gt;&lt;code&gt;writePosgetConsumptionScope&lt;/code&gt;, &lt;code&gt;0x06000254&lt;/code&gt;, RVA &lt;code&gt;0x2CE6C&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Invoke ResetPatcher entry point&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ResetPatcher&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SuspendedObjectCloneHelper&lt;/code&gt;, &lt;code&gt;0x060000C4&lt;/code&gt;, RVA &lt;code&gt;0xA458&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Prepare HTML reset screen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ResetPatcher&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResourceLocatorsetPMDesignator&lt;/code&gt;, &lt;code&gt;0x06000205&lt;/code&gt;, RVA &lt;code&gt;0x2415C&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Build branch-specific RunOnce cleanup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ResetPatcher&lt;/td&gt;
&lt;td&gt;&lt;code&gt;UnsafeAddrOfPinnedArrayElementFindTokenByIndex&lt;/code&gt;, &lt;code&gt;0x06000208&lt;/code&gt;, RVA &lt;code&gt;0x2652C&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Write RunOnce value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ResetPatcher&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ISOCurrencySymbolResourceTableMappingEntry&lt;/code&gt;, &lt;code&gt;0x0600024D&lt;/code&gt;, RVA &lt;code&gt;0x2DDE8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Start and suspend Sysprep&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native 64-bit component&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x140001B30&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PID registration and injection setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native rootkit DLL&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x180001EB0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Install API hooks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native rootkit DLL&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x18000221C&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Filter &lt;code&gt;$sxr&lt;/code&gt; process entries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native rootkit DLL&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x180002444&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Request child-process injection before thread resume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native rootkit DLL&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x180002D78&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Filter NSI TCP/UDP enumeration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native 64-bit component&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x140001F94&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Receive child PID; call injector; acknowledge&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;/details&gt;&lt;details&gt;
&lt;summary&gt;SHA-256 hashes&lt;/summary&gt;&lt;div class=&quot;table&quot;&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Recovered component&lt;/th&gt;
&lt;th&gt;SHA-256&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Initial wrapper&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ac562299cd216585d58cab4c435c1578f3e451820a4c0feb2d902d0662645446&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auxiliary assembly&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0664673cd5981106172b8df9f730afa55247bf6943e5ca4f7eb7d8be2e0a15ee&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unwrapped installer&lt;/td&gt;
&lt;td&gt;&lt;code&gt;37d456e053173d1940995ab8da929b3409d782648e78fef43389a9616d24dc84&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unwrapped &lt;code&gt;Quasar.Client&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;8e73056a2c43dc86ba78a7858bb06c417dc8c59e8cc624e8226ebc7d9168fded&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;div class=&quot;table&quot;&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;SHA-256&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;InstallStager&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fef344668a48c83d7a46beb4eafb35d553c2ef91dcf976b108b2dd89fb8633c4&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;service64&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2981a03b5180831d79d5927053365b89ee538d7259909159f9fb5c219d347207&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rootkit64&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2ee5bacb1efaf063896ce48066a50c1636760ef6c74510b4b2c855444242b1cd&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ResetPatcher&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;eb7990f280afccc1f81e2a7f6abd8e2546a741ad9bc5fef2e1ee5b6c61cf9c94&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ULLH_KeepFiles&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3c13be6f009763aea31e55cd36b137c7fcc386a155f5a6a5dbfb0fe8cf9564c8&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ULLH_RemoveAllFiles&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;6ab4b7ba75e0d9a23ce0c38f0df8ff07d6dba88cc08f6944af4fbc9d5b2f77f3&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;ResetHTML resource: &lt;code&gt;854f3cda916a2ecd56ffb32ecce92ef62552d3f0e4b3aca8dfc320cdf073a52b&lt;/code&gt;.&lt;/p&gt;
&lt;/details&gt;&lt;details&gt;
&lt;summary&gt;Program Files and Program Files (x86): 42 names&lt;/summary&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Windows Defender
Windows Mail
Windows Media Player
Windows NT
Windows Photo Viewer
Windows Portable Devices
Windows Sidebar
WindowsApps
WindowsPowerShell
Windows Accessories
Windows Administrative Tools
Windows Alarms &amp;amp; Clock
Windows Calculator
Windows Camera
Windows Console
Windows Feedback Hub
Windows File Explorer
Windows Hello Face Software
Windows Maps
Windows Multimedia Platform
Windows Narrator
Windows PowerShell Modules
Windows Quick Assist
Windows Security
Windows Subsystem for Linux
Windows System
Windows Terminal
Windows Update
Windows Voice Recorder
Windows Weather
Windows WIA Scan
Windows Defender Advanced Threat Protection
Windows Live
Windows Live Mail
Windows Live Messenger
Windows Live Writer
Windows Multimedia Platform x64
Windows Multimedia Platform x86
Common Files
Internet Explorer
ModifiableWindowsApps
Microsoft
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;&lt;details&gt;
&lt;summary&gt;ProgramData: 16 names&lt;/summary&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Microsoft
Windows
Windows Defender
Windows NT
Windows Photo Viewer
Windows Portable Devices
Windows Sidebar
Windows Defender Advanced Threat Protection
Microsoft OneDrive
Packages
regid.1991-06.com.microsoft
SoftwareDistribution
ssh
USOPrivate
USOShared
WindowsHolographicDevices
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;&lt;details&gt;
&lt;summary&gt;Shared Start-menu Programs: 16 names&lt;/summary&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Microsoft Edge.lnk
Microsoft Edge Beta.lnk
Microsoft Edge Dev.lnk
Microsoft Office
Windows Accessories
Accessories
Administrative Tools
Windows Administrative Tools
Windows PowerShell ISE.lnk
Windows System
Windows Terminal.lnk
Accessibility
Windows PowerShell
Windows Tools
System Tools
Maintenance
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;&lt;p&gt;Sources: &lt;a href=&quot;https://bazaar.abuse.ch/sample/4c6e90e178396d000b5dd5c5bb2b9ae5bbbca5986f26ffad2a6bd0845b6b2c83/&quot;&gt;sample record&lt;/a&gt;, &lt;a href=&quot;https://www.levelblue.com/blogs/spiderlabs-blog/seroxen-rat-for-sale&quot;&gt;earlier SeroXen research&lt;/a&gt;, &lt;a href=&quot;https://github.com/bytecode77/r77-rootkit/tree/134320f841133c9adf15f78d64b3ed1c86042b05&quot;&gt;bytecode77&amp;#39;s r77 source&lt;/a&gt;, &lt;a href=&quot;https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/sysprep-command-line-options?view=windows-11&quot;&gt;Sysprep command-line documentation&lt;/a&gt;.&lt;/p&gt;</content>
  </entry>
</feed>
