NoisyS0cks: Undocumented SOCKS5 Pivot Framework Giving Ransomware Affiliates a Foothold Inside Networks

The WatchGuard Attestation Team has uncovered an undocumented pivot framework written in Golang that opens a Smux-multiplexed SOCKS5-style pivot channel on each compromised host using one of two interchangeable transports: KCP-over-UDP with DTLS obfuscation or Noise-over-TCP with TLS obfuscation. The dual-faced implant is config-driven, either as a service or through an embedded configuration deployed at runtime. It masquerades as the legitimate Windows service TieringEngineService.exe in what was believed to be a RedSun exploit because of the name. When executed, the service is then hidden under a non-default scheduled-task folder (\TaskGlobal\) and registered with a fake Microsoft Edge prefix name (MicrosoftEdgeUpdateTaskMachineCore_) using the verbatim Microsoft-given description for that Edge service. The implant connects to an IP address (67.217.228.51) associated with various phishing and information stealing campaigns, and several ransomware operations. The developers used the name s0cks-noise-v1, and thus, we named it NoisyS0cks

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

The act of pivoting is a lateral movement technique that implies that an attacker has compromised a host and is using tools and techniques as a steppingstone to gather information on other internal systems. A pivoting framework, in conjunction with the act of pivoting, is a tool with architectural design and reusability. There are artifacts throughout this analysis that indicate an intent to design further and evolve this tool. Pivoting, however, has no offensive capability, and therefore, there are no offensive and malicious capabilities embedded within this framework. It’s explicitly for persistence and information gathering. Thus, further malware, such as defensive countermeasure disablers, information stealers or ransomware would follow this. 

Static File Analysis

The analysis begins with a look at the file characteristics. It claims to be the legitimate Storage Tiers Management executable by Microsoft. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

Although it’s a “Microsoft-authored” file, static file header analyzers are unable to determine the coding language and detects a suspicious overlay; one the legitimate TieringEngineService.exe doesn’t have. It also has high entropy indicating it’s packed. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

 

A quick peek at the strings showed Golang artifacts throughout. Running the file through GoReSym shows that all the metadata was stripped except for the Golang version – v1.22.0. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

 

Filtering for ‘FullName’ in the GoReSym output provides a list of function names. It shows that this file was obfuscated with garble, an open source Golang code obfuscator that also strips metadata. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

 

Disassembling the file in IDA reveals all of the garbled functions as well as the main functions: 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

Main 

Main (main_main) begins by establishing garbage collection (GC) at 50% as opposed to the default of 100%. This means once the heap grows by 50% the GC runs. It also sets a memory cap at 64MB. These two functions ensure the process remains small and non-memory consuming. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

It then calls svc.isWindowsService to determine if it is running as a service by checking if the parent process name is ‘services.exe’. Otherwise, it is executed as a standalone process. The execution continues by setting a flag that was able to be revealed by scrapping together various garble-bled strings uncovered during analysis: ‘use –config <file> or embed via builder’

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

 

The flag string reveals several things: 

  • The malware is config driven. 
  • There are two ways for the implant to get its configuration. 
  • A builder exists for this malware. 
  • This specific sample uses the embedded config from the builder. 
  • The config is embedded in the file somewhere. 

After the flag is set, main invokes LoadClientConfig function that loads the configuration mentioned in the flag. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

 

LoadClientConfig  

The execution flow and logic of the malware depend on hard-coded config values and if the config fails to load, it exits. This means that if the config file is removed, renamed, corrupted, and so on, before or after execution, the implant is effectively removed. 

The LoadClientConfig function populates a ClientConfig struct that contains 23 fields and foreshadows its functionality: 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

The struct contains fields for a transport type, server host, KCP and Noise transport configurations, a log toggle, and other network settings. Most of these are accepted as-is by the operator-defined config, except min and max sleep interval boundary checks, PSK key sizing, and one other: Transport. The check for the Transport field checks if the string equals ‘kcp’ or ‘noise’, which defines the dual-transport aspect. 

The config is initialized at runtime. This means we can set a breakpoint in a debugger, such as X64dbg, and extract the entire embedded config after the LoadClientConfig function returns. However, for those more adventurous, most of the encryption artifacts are present to decrypt this offline. We’ve included some of them via a staging function with information about the header, nonce, one of three HKDF inputs, AAD, and ciphertext. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

The AAD is the 14-byte header in red and the ciphertext is at the location in yellow (data_76b7ae). The green at the end is padding. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

Whether it’s decrypted or grabbed at runtime, the official extracted config from the attacker looks like this. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

A couple of important things to extract from this: 

  • Operator set KCP as transport. 
  • The remote IP address uses ports 443 and 853. 
  • At the bottom, KCP obfuscation is DTLS; Noise uses TLS. 
  • The polling interval is a minimum of 10 seconds to a max of 2 minutes. 
  • Each build has an ID and is timestamped. 
  • It sets a task name prefix and description impersonating a legitimate Microsoft service. 

The transport, obfuscation, and port numbers imply that, upon execution, this will send UDP datagrams on port 853 using DTLS obfuscation, making the packets appear as authentic DNS-over-QUIC packets. KCP traffic on port 443 will appear as DNS-over-HTTPS packets. So, the malware intends to send DTLS and UDP datagrams on the wire every 10 seconds to 2 minutes to that C2 IP address, which is hosted by BL Networks, an anonymous virtual private server (VPS) provider highly correlated with ransomware affiliate operations. 

Now that the config is thoroughly understood, the next step in main is to see what the config is applied to and what actions are performed based on the values given. 

Client Struct  

First, a Client object is created with the config, a value is applied to stopCh (stop channel), and then it runs.  

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

The client struct is visible just as the client config is: 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

A client has three components: 

  • cfg – The config 
  • stopCh – a stopCh (stop channel) struct 
  • destroyOnce – a destroy function that is guaranteed to only run once 

These three fields are effectively run, pause, and destroy. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

Client_Run 

Client_Run is where most of the action happens. It begins with an indefinite loop (while(1)) after attempting some logging setup, which was deactived by the operator via the config setting. It then grabs the config-defined minimum sleep duration and max duration and calls client_jitter. The jitter function selects a random time between the min and the max duration and is applied to the client poller. In lay terms: the implant will poll at a random time between 10 seconds and two minutes. 

PollOnce 

After staging, it calls PollOnce, which is a branching function to either the Noise transport PollOnce function or the KCP PollOnce function. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

In this case, the KCP PollOnce transport function is invoked because the operator set the config Transport field to ‘kcp’. However, as a preface, these two poll functions are identical except for nuances in transport structure, not execution. They apply the respective transport settings from the operator-defined config, apply a 10 second poller timeout, then listen for dispatch commands from the operator. 

At the end of the pollOnce function, the malware begins to set up the operator controller logic. It reads in the first three bytes from the payload header and branches based on which byte the implant receives. An error message observed in the code reveals the first byte is the ‘version’ and the second byte is ’type’. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

 What type means here could only be assumed. The first byte we theorize is the observed version at the beginning of this post – s0cks-noise-v1. Thus, the hard-coded value is ‘1’. The second byte, the type, is a hard-coded value of ‘16’.  The third byte is the dispatch command. It switches on values 1,2, or 3. It also has a default case which returns the execution flow back to the poller, repeating the cycle. Therefore, the dispatch commands are as follows: 

  1. active 
  2. heartbeat / no-op 
  3. destroy 

The picture below shows the check for the first and second bytes, and flows through a switch statement based on if the third byte is 1, 2, 3, or default (anything other than 1, 2, or 3). 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

Visually, the C2 reply header looks like: 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

Active (1) 

activeLoop 

If the command dispatch byte equals 1, then the implant runs the ‘activeLoop’ function, which hosts the primary functionality of this pivot framework. Once this command is received by the operator, the implant knows to wake up and start taking actions. It’s no longer polling at this point. The execution begins with an inner loop–hence the name, activeLoop—and begins actions until the operator pauses or destroys the implant completely. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

Once the client is active, it establishes three primary listeners: a polling watchdog listening for the stopCh command (dispatch command 2), a keepDataAlive heartbeat, and an error handler that leads to doDestroy (dispatch command 3). 

After these listeners are established, activeLoop’s primary functionality is in the dialDataSession function that returns a smux session, which is particularly revealing. 

dialDataSession 

The dialDataSession function does the same KCP versus Noise transport check as earlier and is responsible for establishing the control and data channel pairings to act as an additional encrypted channel to the C2. The control channel is for signaling and listens for incoming C2 commands on port 443. The data channel is for transport and is only in use when the implant is active (command dispatch 1) on port 853. The data channel is more commonly referred to as a tunnel. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

Each KCP channel derives its own AES-256 key during session setup using the same master PSK from the config combined with a channel-specific HKDF info tag. Control and data channels therefore encrypt under different keys despite sharing a root secret. Thus, recovering one channel’s traffic does not enable decryption of the other. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

DialDataSession continues by doing another trivial check for ‘dtls’ and calls NewDTLSDisguiseConn that wraps the KCP bytes into a properly formatted DTLS record and passes it to NewConn. In other words, this function acts as a middle man between KCP and UDP that wraps the KCP data with DTLS and sends it to the wire via UDP. This happens in reverse on the receving machine. UDP datagrams get parsed as DTLS records, decrypted, and sent to KCP handlers. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

The function finishes by tweaking settings and returning a smux client session. If the implant can’t open the data channel session, it waits for three seconds and tries again. After three failed attempts it gives up (nine total seconds) and goes back to the main poller, waiting for an active, heartbeat, or destroy dispatch command. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines
IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

After the smux client session is successfully instantiated, it sends it’s first message back to the C2 using a specific format: 

HELLO %016x 

This string was uncovered in the obfs_init blob decrypted at runtime, among many other important string artifacts. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

The 22-byte ASCII string fits perfectly with the client ID provided in the config, which is 16 bytes (“client_id”: 16377594508454902104). Converting the decimal client ID to ASCII shows the exact first bytes of a smux session for this particular config: 

HELLO e3603fa31c3cc358 

In code, this appears as a call to convT64 and then a call to Fprintf. The location of the client is qword_91AE60 and the length of the data is at qword_91A368. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

An implant using a hardcoded client ID for communication instead of other fingerprinting mechanisms ensures that the implant persists and is identifiable if the machine moves networks with IP changes and NAT changes, and can even allow the operator to change C2 infrastructure while still being able to identify the implant. 

The activeLoop function continues by calling an AcceptStream function that blocks open data channels until the operate initiates a new one. If the operator invokes a new session, it hands back a fresh stream object. All in all, this means that one encrypted tunnel can carry many concurrent pivot streams. The data channel is the tunnel and smux is the multiplexer that lets multiple independent logical streams ride concurrently inside it. Each stream object created by dialDataSession is passed to handleStream which handles the entire lifecycle of one pivot stream. 

handleStream 

Once a stream is created and accepted, it is then handled. DialDataSession stages the stream, and handleStream, well, handles it. It does this starting with parsing a small SOCKS5-like request that the operator sends as the first bytes of the stream. That header data is used to dial the TCP target within the header, signal back, and then hand it off to a final function called bridge to relay bytes back and forth. After bridge returns, the stream is cleaned up and frees up space for another stream. The final questions for an active session remain: What does the SOCKS5-like request look like and what does bridge do. 

The NoisyS0cks implant leverages a trimmed version of SOCKS5 as defined in RFC 1928. It includes: 

  • VER 
  • ATYP 
  • ADDRESS 
  • PORT 

And excludes: 

  • Method-negotiation handshake 
  • Authentication sub-negotiation 
  • CMD byte 
  • RSV byte 

It excludes the handshake and authentication because the underlying KCP and DTLS PSK already proves identity; it’s redundant, and the CMD and RSV have no functionality within this framework. 

Therefore, the SOCKS5-style header used by this implant looks like: 

[VER][ATYP][ADDRESS][PORT] 

Where: 

  • VER: (1 byte) hardcoded 0x01 value. 
IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines
  • ATYP: (1 byte) 
  • 0x01: IPv4 address 
  • 0x03: Domain 
  • 0x04: IPv6 address 
  • The implant explicitly prohibits IPv6 addresses and exits to the main poller if one is provided. 
  • ADDRESS: (1-256 bytes) IPv4 address value 
  • PORT: (2 bytes) port number 

Therefore, we can use an example from this operator to see what the SOCKS5-like header would look like, for example, if an operator wanted to reach the domain controller at 10.0.1.5 on port 445 (SMB), the attacker would send that command down the wire via a new smux session and the header appears as: 

  • VER: 1 (0x01) 
  • ATYP: 1 (0x01) 
  • ADDRESS: 10.0.1.5 (0x10 0x00 0x01 0x05) 
  • PORT: 445 (0x01BD) 
IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

After the header is parsed, the TCP socket is tuned with NoDelay, a KeepAlive setting set to 60 seconds, and 256 KB buffers. This is all offloaded to the final primary function in an active session: bridge. The bridge function is where the implant stops being a parser and becomes an active relay. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

Bridge establishes two countingReader and countingWriter pairs. One for the smux stream and the other for the TCP socket. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines
IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines
IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines
IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

This function bridges the TCP socket with the smux stream that allows for birectional traffic to flow within the tunnel. 

Each reader and writer pairing has a five second tick with a five-minute idle threashold. If both directions sit silent for five minutes, the pivot is torn down. Although, all other pivot streams continue as normal. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines
IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

Finally, after all the setup is done, it launches two helper goroutines. One copies bytes from the smux stream to the TCP socket and the other to launch the idle watchdog. The parent goroutine handles the reverse direction of the relay. From that point on, three workflows run concurrently: operator bytes shuttling to the target, target bytes shuttling back to the operator, and the watchdog ticking in the background. Both copy loops use pooled buffers and run until one side closes the connection. 

To summarize the active dispatch command section, it ultimately grabs the values from the client config which drives how data will be transported from the machine back to the C2, and vice versa. The Transport field steers how the communication will develop further. If the operator chooses KCP, then UDP is utilized for communication, else if the operator choose Noise, then TCP is used. Furthermore, the config has fields for obfuscation for each transport type: DTLS for KCP and TLS for Noise. Then, the malware sets up a smux multiplxer to manage multiple streams regardless of the transport type. The multiplier handles various streams defined in the SOCKS5-style header given by the operator. 

Heartbeat/no-op (2) 

The heartbeat dispatch command is uneventful, and that’s by design. It’s purpose is to continue polling. That’s it. It sets a poll counter to 0 and loops through. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

Destroy (3) 

Referencing the client struct, it contained a config, stop channel inner struct, and a destroyOnce sync.Once object. The stop channel struct effectively lets the implant know the operator is done for now. Whereas the destroyOnce object tells the implant the operator is done for good. Once the operator provides a destroy command (3), the switch statement triggers a doDestroy command to begin the destruction process. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

The doDestroy function locks the destroyOnce object in the struct and points to an inner function at offset 721398 (off_721398). 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

This function launches LaunchSelfDestruct and then sleeps for 3 seconds. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

LaunchSelfDestruct is always called at the end of execution, either by the operator invoking it directly or if an error occurs in activeLoop. This means that the primary exit function in main never gets called. Either the operator will destroy the implant directly or it breaks, otherwise it will poll indefinitely. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

It begins by marking the service for deletion from the Service Control Manager (SCM) database registry. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines
IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

A BAT file helper is invoked that creates a BAT file called rsdel_*.bat, where the wildcard symbol is a random string. The BAT file can also be extracted from memory at runtime, just like the config and other important values. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

Rewritten for readability: 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

Aside from the logging instructions, which were disabled for this sample, this BAT file performs three primary tasks: 

  1. Attempts 30 times to delete the binary with a sleep between each attempt. The “sleep” is three ping attempts to local host 
  2. Delete 
  3. If failed, ping localhost three times 
  4. Tries up to 30 times 
  5. Remove the scheduled task 
  6. Deletes itself (the BAT file) 
IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

If the BAT file fails to run, the last step for the entire execution is to schedule the binary to delete itself after reboot. The last fallback mechanism for cleanup in the execution. It then returns to the doDestroy function and sleeps for the defined 3 seconds, and execution ends. 

PCAP 

The majority of information was extracted during static file analysis, but seeing a packet capture of execution provides visual verification of the obfuscated packets on the wire. Since the operator chose KCP with DTLS obfuscation, the packets appear as UDP datagrams with the occassional DTLS datagram. 

IDA view showing NoisyS0cks main functions, including service execution and scheduled task creation routines

The UDP datagrams are the outbound packets from the victim machine on port 443. These are the control frames. No additional data is flowing through them, only the dispatch commands change the value (1, 2, or 3). This also explains why each datagram has a length of 77 bytes; it sends the same data length every time. 

The DLTS datagrams, on the other hand, are the inbound traffic from the operator. When an operator opens a pivot, they dial the destination on the victim machine that’s provided in the SOCKS5-style header. The header provides the given IPv4 address or domain and a port number, gets handled, and a bridge is opened up in the tunnel. Since relay data is flowing through, the size will alter slightly, as is shown in the PCAP. 

Therefore, not only does the PCAP show the file in execution, but it also is an actual capture of the operator sending commands to the testing sandbox enviroment. 

Conclusion 

In summary, NoisyS0cks, or s0cks-noise-v1, isn’t just an implant, a tunnel, or a fully ingrained RAT. It’s a pivot framework with indicators for further development such as versioning, verbose logging, graceful error catching, and being config-driven. It instills persistence on a machine and installs two multiplexed birectional relays, one for control flow and the other a data tunnel. Within this tunnel, an operator can invoke a bunch of pivot sessions concurrently. This means that an operator can pefrorm various tasks on their own machine, with their own tools, and it all be passed through the tunnel. In other words, all of these tasks can be performed at the same time: 

  • SMB (445) and LDAP (389) enumeration using Bloodhound 
  • MySQL recon (3306) 
  • Kerberoasting using port 88 
  • SSH (22) pivoting 

And so on. The SOCKS5-like header contains the port numbers along with the IPv4 address or domain where these actions are performed. The relay has no idea what bytes are being passed through, it simply moves them along. 

Given the C2 infrastructure is associated with information stealing campaigns and ransomware groups, indicators of NoisyS0cks on a machine are sure to preempt a subsequent information stealer or RAT, or even ransomware, further down the attack chain. This pivot framework is a foothold onto a system; one of the first steps. 

IoCs 

MD5  0ee434b22145bE65a7c93646ab08c636 
SHA1  c77271ab9c621ae8dd6783fd6b45c306a9472230 
SHA256  e683d6f736d56dd7d8696887bb7e3d407a45cce40afbf9d7c4a9cf1547957143 
C2 IP  67.217.228.51 
File Path  C:\Windows\SysWOW64\TieringEngineService.exe 
Task name Prefix  MicrosoftEdgeUpdateTaskMachineCore_ 
Task Folder  \TasksGlobal\* 
Batch Deletion Script  %TEMP%\rsdel_*.bat 
Scheduled Task  %TEMP%\task_*.xml 

YARA 

rule NoisyS0cks_v1 { 

meta: 

description = "Detects NoisyS0cks V1" 

author = "Ryan Estes" 

date = "2026/05/28" 

strings: 

$go_init = "runtime.g" 

$kcp = { 6b 63 } 

$noise = { 6e 6f 69 73 } 

$kcp_dtls = { 64 74 6c 73 } 

$noise_tls = { 74 6c } 

$config_transport = "Transport" ascii 

$config_kcppsk = "KcpPSK" ascii 

$config_sleep_min = "SleepIntervalMin" ascii 

$config_sleep_max = "SleepIntervalMax" ascii 

$config_server_host = "ServerHost" ascii 

$rsdel_prefix = "rsdel_" ascii 

condition: 

($go_init) and 

(all of ($kcp*) or all of ($noise*)) and 

all of ($config) and 

($rsdel_prefix) 

External References 

Tools Used 

  • Binary Ninja 
  • Censys 
  • CyberChef 
  • Detect it Easy (DiE) 
  • GoReSym 
  • IDA 
  • Notepad++ 
  • PEStudio 
  • PowerShell 
  • PPEE (puppy) 
  • Regshot 
  • System Informer 
  • VirusTotal 
  • Wireshark 
  • X64DBG