ReversingLabs researchers identified a debugger evasion method in the QuantumLocker malware family that was not included in MITRE’s Malware Behavior Catalog. It has now been added as Exception Flooding. This blog recounts the history of QuantumLocker and how this particular debugger evasion method may appear in future iterations by the same adversary or elsewhere. We then share a set of YARA rules your team can use to detect it.
Overview
Ransomware is an ever-present cybersecurity threat. However, the adversary groups who operate ransomware are continually breaking up and reforming, often rebranding themselves. For instance, the Conti ransomware gang disappeared from the threat landscape after being active for less than two years. However, the malicious actors behind this operation did not disappear. They regrouped, forming new alliances to create new ransomware gangs. During these reformulations, malicious code is often reused and updated. Additionally, code and methods previously written for different malware can be incorporated into these new versions. Finally, successful methods used by other malware families can be mimicked in new code implementations.
This is exactly what happened after Conti’s demise. The core source code of the ransomware family was reused and updated, retaining many features, adding others, and refining various anti-analysis features. Some of them were implemented in a succession of malware, from MountLocker to QuantumLocker to DagonLocker.
Here, we are revisiting QuantumLocker’s history to focus on a type of debugger evasion which had not yet been included in MITRE’s Malware Behavior Catalog. While QuantumLocker is no longer an active threat to organizations today, it is possible that this evasion method will reappear in new attacks or be used by other malware families. ReversingLabs researchers created YARA rules that can help security teams identify the method outlined here.
Threat actors recycle (code, that is)
QuantumLocker was a ransomware family which was active for about one year from December of 2021 until November of 2022. It was a rebranding of a former iteration called MountLocker, which first appeared in July of 2020. Later came two additional offshoots named AstroLocker and XingLocker, which were alternative brandings of MountLocker. A major code update occurred when the adversary operating this ransomware rebranded from MountLocker to QuantumLocker. That included the obfuscation of strings embedded in the sample through the addition of a string decoding function.
Code reuse across rebrandings is common with ransomware. A further iteration near the end of QuanumLocker’s active period was another code update and rebrand called DagonLocker. The known DagonLocker samples were compiled in August of 2022 and were active in attacks targeting South Korea. There was a period of overlap while both DagonLocker and QuantumLocker were active at the same time.
In their attacks, Quantum would use a technique to gain entry into the attacked system, such as a phishing email followed by the use of Emotet, a malware also used by Conti to attack its victims. Once a foothold had been established, the adversary moved laterally within the victim’s network. This stage of the attack often employed components of Cobalt Strike, a penetration testing tool often used by ransomware gangs. This is also the stage of attack where sensitive data was exfiltrated to be used for extortion on top of the ransom. The last stage of attack would be to deploy the ransomware across domain controllers and other workstations.
Today, our industry is well aware of Quantum’s attack methodology. It is fair to say that future offshoots and rebrands will reuse code seen in these past iterations. Organizations need to remain vigilant and look out for methods such as this type of debugger evasion in the future.
For your security team: Open-source YARA Rules
ReversingLabs has identified a debugger evasion method that poses a problem for analysts. This method had not been described in the Malware Behavior Catalog (MBC), a framework for classification of malware based on a standardized taxonomy of behaviors, features, and objectives. The goal of the project is to provide a language for labeling, similarity analysis, and standardized reporting. After a discussion among MBC project members, this debugger evasion method was added as Exception Flooding and given the ID B0002.031.
A debugger is a tool that malware analysts and reverse engineers use to examine an executable malware sample step by step, allowing them to observe malware behavior as each instruction is executed. Samples used in this research contain a large number of function calls that cause a denial of service (DoS) on a debugger. The specific debugger in this case is x64dbg.
Two Examples of Exception Flooding via lstrcpyA
The two sets of instructions in the figure above are function calls to lstrcpyA. Both achieve the same purpose: generating an access violation. This function, when used in benign code, copies a string from one location in memory to another. The first argument to the function, lpString1, is the address of the destination buffer where the string is copied to.
In QuantumLocker, this destination address is located in a section of the file that is read-only. This raises an access violation which leaves the debugger in a paused state waiting for user input to continue execution. There are so many of these that without a mitigation, debugging is extremely slow and frustrating. Each of the different variations of this function call have effectively the same purpose: they take a junk string of random data and try to copy it to a read-only section of the file.
Some instances of this function call also exhibit a separate anti-analysis method called code interleaving. This is where assembly instructions that belong logically to a previous or subsequent action are inserted between instructions of this function call. The goal of code interleaving, if added manually by the adversary, is to make writing a detection rule more difficult. Code interleaving can also occur due to benign compiler optimizations. The set of YARA rules for detecting exception flooding does account for code interleaving under certain conditions.
Code interleaving: Unrelated instruction between function call and parameters.
The YARA rules provided below this blog post detect three variations of the above method as found in QuantumLocker malware samples. These rules additionally leverage a new feature in YARA, pe.import_rva, which allows for easy detection of the name of a called function in a particular location in or around a matched string. This new feature is included in the latest YARA release and soon to be integrated with ReversingLabs solutions. In the use case presented here, this feature is particularly useful for identifying the call to lstrcpyA.
Software bugs in benign files
Because this function call does not cause a program to crash during normal execution, these YARA rules can also potentially match on benign files that include these instructions by mistake in the form of a software bug.
These instructions found in the benign files were verified in a debugger to make sure that the behavior in this case is the same as in the QuantumLocker samples.
Software bug raising access violation in a benign file.
Note that in the benign files this bug only occurred once. However, in QuantumLocker and in any other potential files that use this debugger evasion, the instructions would need to occur many times for the evasion to be effective. The rule which matched benign files was adjusted to correct for this and to remove the false positives.
Microsoft’s documentation states the reason these calling conditions in the lstrcpyA function don’t cause a program to crash during normal execution:
This function uses structured exception handling (SEH) to catch access violations and other errors. When this function catches SEH errors, it returns NULL without null-terminating the string and without notifying the caller of the error.
(A tip of the hat to malwarefrank for pointing this out.) This particular debugger evasion has some similarities to tricks documented by Peter Ferrie in The ‘Ultimate’ Anti-Debugging Reference (2011). In chapter 7 sections A and D.v, the heap functions that are used to detect a debugger register their own internal structured exception handler in a similar way to how these Windows Base API (winbase) functions do, except the effect is used differently in the evasion described here. Furthermore, in section G.xii the concept of an intentional exception is described. This is what is seen here in this debugger evasion, except that the purpose of the action is not to trigger adversary code located in a SEH registered by the program itself earlier.
Mitigations
One of many options for mitigating this type of debugger evasion is to perform a live patch of the file during analysis to replace these instructions with NOP (No Operation) bytes thus allowing execution to move past the problem.
Replace debugger evasion with NOPs in x64dbg.
Alternatively, by knowing the exception code in advance: C0000005, it can be set in the debugger’s exception filter. For x64dbg specifically, without knowing an exception code in advance, the “Ignore Last” feature can be used to automatically add the most recent exception to the filter. (The Ignore Last feature is found in the Preferences pane on the Exceptions tab.)
A word of warning about this mitigation, however: It can hide other instances of access violations the analyst may be interested in besides this debugger evasion.
Add access violations to x64dbg exception filter.
Other variations
The function lstrcpyA is not the only function in winbase that exhibits this behavior. A test file with examples of lstrcpynA, lstrcatA, lstrcpyW, lstrcpynW, and lstrcatW was compiled to demonstrate this.
Starting with the examples observed in QuantumLocker along with the variations from the synthetic file generated above, a set of possible permutations was determined. These permutations were searched for in recent malware samples to see if any of these other variations were in use elsewhere. This search surfaced a handful of 64 bit files which all were benign and fit into the category of software bug. Additionally, in each of these files, the software bug only occurred once or twice which is not enough for this evasion to be effective. Adjusting the YARA rules to correct for how many instances occur in one file corrected for all of these false positives and still matched all of the QuantumLocker samples.
In the 32 bit space, there were many more matching files both benign and malicious. Some of these files were quite old ranging back to 2009 and 2010. However, on manual inspection of each file, none were determined to contain this particular debugger evasion. All of the files fit into one of two categories: file corruption or software bug. The corrupt files contained the series of bytes making up the targeted instructions but the location of the bytes was not disassembled correctly. Additionally, in almost all cases, the instructions occurred only once, and in a small number of cases twice. This indicates that all of these instances, even the ones in malicious files, are software bugs since exception flooding requires there to be many instances in a sample. Visual inspection and manual disassembler analysis show that the location where the instructions occur is often immediately after a control flow feature such as a compare or test instruction and the software bug is located in one of two alternative execution pathways.
(On a side note, an explanation for the larger number of examples of these instructions in the form of a software bug in 32 bit samples as opposed to 64 bit samples may have to do with all of these functions now being deprecated by Microsoft, as noted in the documentation. Recent versions of Visual Studio not allowing builds to complete if this bug is encountered in the source code is certainly another contributing factor.)
Conclusion
ReversingLabs researchers hope that security teams will be vigilant for QuantumLocker’s offshoots and use best practices to defend their organizations. Known for reusing code, anti-analysis features may be reused in future iterations of ransomware from the same adversary. Here, QuantumLocker has been observed employing a debugger evasion method that was previously not described in MITRE’s Malware Behavior Catalog. This method, now designated as Exception Flooding, can be detected going forward by the following YARA rules.These rules are available on the ReversingLabs GitHub page, as well as many other open-source YARA rules written by our team for a variety of threats.
YARA Rules
import "pe"
rule AntiDebugging_ExceptionFlooding_1 : M_B0002 A_T1622
{
meta:
author = "Malware Utkonos"
organization = "ReversingLabs"
date = "2023-03-28"
description = "Detects exception flooding, an anti-debugging technique, which uses lstrcpyA to write a junk string to a read-only section raising an access violation"
exemplar = "2fd8356abd42b19799aca857990a5f49631b02bd3253f80d96b5d27dcfd2f7c9"
location = "0x1400040a6"
api_documentation = "https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-lstrcpya"
mitre_att = "T1622"
mitre_mbc = "B0002.031"
strings:
$op = {
488d1d[4] // lea rbx, [rel junk_dest]
488bcb // mov rcx, rbx {junk_dest}
488d15[4] // lea rdx, [rel junk_source] {"eRgPKQSc"}
ff15 // call qword [rel lstrcpyA]
}
condition:
for any i in (1..#op) : (
pe.sections[pe.section_index(@op[i])].characteristics & pe.SECTION_MEM_EXECUTE and
not pe.sections[pe.section_index(pe.rva_to_offset(@op[i] + 7 - pe.sections[pe.section_index(@op[i])].raw_data_offset + pe.sections[pe.section_index(@op[i])].virtual_address + uint32(@op[i] + 3)))].characteristics & pe.SECTION_MEM_WRITE and
for any imp in pe.import_details : (
for any fun in imp.functions : (
fun.name == "lstrcpyA" and
@op[i] + !op[i] + 4 - pe.sections[pe.section_index(@op[i])].raw_data_offset + pe.sections[pe.section_index(@op[i])].virtual_address + uint32(@op[i] + !op[i]) == pe.import_rva(imp.library_name, "lstrcpyA")
)
)
)
}
rule AntiDebugging_ExceptionFlooding_2 : M_B0002 A_T1622
{
meta:
author = "Malware Utkonos"
organization = "ReversingLabs"
date = "2023-03-28"
description = "Detects exception flooding, an anti-debugging technique, which uses lstrcpyA to write a junk string to a read-only section raising an access violation"
exemplar = "2fd8356abd42b19799aca857990a5f49631b02bd3253f80d96b5d27dcfd2f7c9"
location = "0x140004235"
api_documentation = "https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-lstrcpya"
mitre_att = "T1622"
mitre_mbc = "B0002.031"
strings:
$op = {
488d15[4] // lea rdx, [rel junk_source] {"ScfRhqZ"}
488d0d[4] // lea rcx, [rel junk_dest]
ff15 // call qword [rel lstrcpyA]
}
condition:
for 2 i in (1..#op) : (
pe.sections[pe.section_index(@op[i])].characteristics & pe.SECTION_MEM_EXECUTE and
not pe.sections[pe.section_index(pe.rva_to_offset(@op[i] + 14 - pe.sections[pe.section_index(@op[i])].raw_data_offset + pe.sections[pe.section_index(@op[i])].virtual_address + uint32(@op[i] + 10)))].characteristics & pe.SECTION_MEM_WRITE and
for any imp in pe.import_details : (
for any fun in imp.functions : (
fun.name == "lstrcpyA" and
@op[i] + !op[i] + 4 - pe.sections[pe.section_index(@op[i])].raw_data_offset + pe.sections[pe.section_index(@op[i])].virtual_address + uint32(@op[i] + !op[i]) == pe.import_rva(imp.library_name, "lstrcpyA")
)
)
)
}
rule AntiDebugging_ExceptionFlooding_3 : M_B0002 M_B0032_014 A_T1622
{
meta:
author = "Malware Utkonos"
organization = "ReversingLabs"
date = "2023-03-28"
description = "Detects exception flooding, an anti-debugging technique, which uses lstrcpyA to write a junk string to a read-only section raising an access violation"
exemplar = "2fd8356abd42b19799aca857990a5f49631b02bd3253f80d96b5d27dcfd2f7c9"
location = "0x140005cdb"
api_documentation = "https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-lstrcpya"
mitre_att = "T1622"
mitre_mbc = "B0002.031"
mitre_mbc = "B0032.014"
strings:
$op = {
488d2d[4] // lea rbp, [rel junk_dest]
0f2978d8 // Interleaved instruction
488bcd // mov rcx, rbp {junk_dest}
488d15[4] // lea rdx, [rel junk_source] {"yHOnFYVN"}
ff15 // call qword [rel lstrcpyA]
}
condition:
for any i in (1..#op) : (
pe.sections[pe.section_index(@op[i])].characteristics & pe.SECTION_MEM_EXECUTE and
not pe.sections[pe.section_index(pe.rva_to_offset(@op[i] + 7 - pe.sections[pe.section_index(@op[i])].raw_data_offset + pe.sections[pe.section_index(@op[i])].virtual_address + uint32(@op[i] + 3)))].characteristics & pe.SECTION_MEM_WRITE and
for any imp in pe.import_details : (
for any fun in imp.functions : (
fun.name == "lstrcpyA" and
@op[i] + !op[i] + 4 - pe.sections[pe.section_index(@op[i])].raw_data_offset + pe.sections[pe.section_index(@op[i])].virtual_address + uint32(@op[i] + !op[i]) == pe.import_rva(imp.library_name, "lstrcpyA")
)
)
)
}
- Tags:
- Yara Rules