Researching FORCEDENTRY: Detecting the Exploit With No Samples
This FORCEDENTRY post is authored by Matt Suiche (Director, Memory, IR & R&D).
Earlier this month, I reached out to my friend Valentina and told her I wanted to learn about macOS/iOS exploitation, so she recommended taking a look at the CVE-2021-30860 vulnerability, also known as FORCEDENTRY, and the prior work her friend Jeffrey Hofmann posted on Twitter.
One year ago, Google Project Zero published an analysis of the NSO iMessage-based zero-click exploit caught in-the-wild by Citizen Lab and was dubbed as “one of the most technically sophisticated exploits we’ve ever seen” by the Google Project Zero team.
Instant messaging zero-click exploits have seen increased popularity for various reasons. They are often the result of core system vulnerabilities, making this exploit a good candidate to learn about this popular infection vector. This is true, particularly for defense reasons to understand the high end of the spectrum of threats. Such threats come with a few challenges, including the lack of publicly available samples, which makes it harder to learn about their modus operandi and, therefore, develop new detection techniques without relying on low-quality IOCs such as regexes on process names or simple hashes.
The increasing sophistication of attackers requires detection engineering to also evolve. However, the detection dilemma is often access to high-quality samples. In the case of known malware, this is not a problem, but it is a problem when it comes to sophisticated attackers engaging in targeted attacks.
Goals
The purpose of this blog post and accompanying utility is to provide a better understanding of a sophisticated zero-click attack, learn more about PDF and JBIG2 formats, and macOS/iOS exploitation. Additionally, it seeks to challenge the traditional approach of detection by going beyond regular expressions or process name checks. The utility was written to enable file analysis of a non-fileless attack without possessing any samples. Through this, the blog post and utility aim to provide valuable lessons and insights into the attack.
The FORCEDENTRY Vulnerability
The victim is sent a PDF file with a .gif extension that contains a maliciously crafted JBIG2 object. This object is used to exploit an integer overflow vulnerability in the JBIG2Stream::readTextRegionSeg()
function.
numSyms = 0;
for (i = 0; i < nRefSegs; ++i) {
if ((seg = findSegment(refSegs[i]))) {
if (seg->getType() == jbig2SegSymbolDict) {
// Step 1: int overflow
numSyms += ((JBIG2SymbolDict *)seg)->getSize();
} else if (seg->getType() == jbig2SegCodeTable) {
codeTables->append(seg);
}
} else {
error(errSyntaxError, getPos(), "Invalid segment reference in JBIG2
text region");
delete codeTables;
return;
}
}
(...)
// Step 2: Allocate the undersized symbol bitmaps.
syms = (JBIG2Bitmap **)gmallocn(numSyms, sizeof(JBIG2Bitmap *));
(...)
kk = 0;
for (i = 0; i < nRefSegs; ++i) {
if ((seg = findSegment(refSegs[i]))) {
if (seg->getType() == jbig2SegSymbolDict) {
symbolDict = (JBIG2SymbolDict *)seg;
for (k = 0; k < symbolDict->getSize(); ++k) {
// Step 3: overflows
syms[kk++] = symbolDict->getBitmap(k);
}
}
}
}
Samuel Groß previously wrote an excellent overview of the BlastDoor sandbox, and in a follow-up blogpost with Ian Beer about FORCEDENTRY, they explained that prior to iOS 15.0 (20 September 2021), the IMTranscoderAgent process parsed malicious fake-GIF files outside of the BlastDoor sandbox.
Google Project Zero and Jeff have highlighted that the vulnerability is in the JBIG2 implementation of xpdf
, which has been fixed in version 4.04+. To understand how maliciously crafted PDF files can trigger the FORCEDENTRY exploit, xpdf and its utilities such as pdftopng (v4.03 and lower) can be used on macOS without the need for an iOS device. This also allows for printf()
debugging, which is more comfortable than UNIX debuggers such as lldb.
In order to each the vulnerable code within JBIG2Stream::readTextRegionSeg()
, a JBIG2TextRegionSegment
(TRS) segment must be present, containing multiple references to JBIG2SymbolDictionarySegment
(SDS) segments. The SDS’ num_ex_syms
size field returned by getSize()
must be utilized to trigger the integer overflow.
In order to exploit the vulnerability, the heap memory layout must be arranged (or groomed) such that the allocated syms
array is positioned before segments->data
and pageBitmap
. This can be achieved by creating multiple JBIG2PageInfoSegment
(PIS) which will trigger the JBIG2Stream::readPageInfoSeg()
function and force multiple JBIG2Bitmap
allocations, resulting in the re-arrangement of the memory layout. Each reference to a JBIG2SymbolDictionarySegment
(SDS), inside the JBIG2TextRegionSegment
(TRS), is encoded over 1, 2 or 4-bytes, depending on the range of the segment number (seg_num
).
To write to the fields w
, h
and line
of the pageBitmap
structure, the relative offset from pageBitmap->data
to pageBitmap
must be determined. This offset will be used to access the memory address at which the bits are to be written.
*** variables ***
nRefSegs = 0x20002
numSyms = 0x2
readTextRegionSeg: syms @ 0x12e417c10 with size 0x10
readTextRegionSeg: pageBitmap = 0x12e417d60 (0x150 bytes
after syms)
readTextRegionSeg: pageBitmap->data = 0x12e4178b0 (0x4b0 bytes
before pageBitmap)
readTextRegionSeg: segments = 0x13dfd0b40 (0xfbb8f30 bytes
away from syms)
readTextRegionSeg: segments->data = 0x12e417c60 (0x50 bytes away
from syms)
readTextRegionSeg: globalSegments = 0x13dfd1510 (0xfbb9900 bytes
away from syms)
readTextRegionSeg: refSegs = 0x130018000
*** distance ***
segments->data is 0x50 bytes after syms
pageBitmap is 0x150 bytes after syms
pageBitmap->data is 0xfffffffffffffca0 bytes after syms
pageBitmap is 0x4b0 bytes after pageBitmap->data
The Proof of Concept (POC) demonstrates the use of or_808080h_at_offset_h()
and or_bytes_at_offset_w()
functions to access memory from the relative offset starting at pageBitmap-> data
. The logic is based on crafting JBIG2GenericRefinementRegionSegment
(GRRS) segments to trigger JBIG2Stream::readGenericRefinementRegionSeg()
and then JBIG2Bitmap::combine()
, which implements the weird machine logic with OR
, XOR
, AND
, XNOR
, and REPLACE
opcodes. To access JBIG2Bitmap::combine()
, and therefore leverage the JBIG2 weird machine, two conditions must be met:
pageW
andpageH
must have high values to pass an early check inJBIG2Stream::readGenericRefinementRegionSeg()
andpageBitmap->w
andpageBitmap->h
must have high values to pass a sanity check inJBIG2Bitmap::combine()
.
A new JBIG2PageInfoSegment
can be created to fulfill requirement 1, which would set both pageW
and pageH
to a given value. Requirement 2 is partially fulfilled during the overflow process, where syms[kk++] = symbolDict->getBitmap(k);
fills the buffer with pointers and pageBitmap->h
is set to a high value. pageBitmap->w
is set to 0x1
due to memory address encoding, so or_808080h_at_offset_h()
must be called first to update pageBitmap->w
to a value under 0x7fffffff
(0x00808081
). This allows or_bytes_at_offset_w()
to be used to set h/w/line
to 0x7fffffff
, virtually expanding the fake canvas.
In order to have a fully working exploit, a full write-what-where capability must be achieved, rather than just a relative write. This will be discussed in further detail in a future blog post.
Detection
We now understand how the bug is exploited, and we can create a utility to detect if a file is attempting to trigger this exploitation. Unfortunately, runtime detection on mobile devices is difficult due to operating system constraints, but this still allows us to add a feature to our playground utility to detect this behavior.
cargo run -- --analyze ../xpdf-
4.03/build/xpdf/forcedentry.pdf
Finished dev [unoptimized + debuginfo] target(s) in 0.03s
Running `target/debug/elegant-bouncer --analyze ../xpdf-
4.03/build/xpdf/forcedentry.pdf`
elegant-bouncer v0.1
ELEGANTBOUNCER JBIG2/PDF scanner for FORCEDENTRY (CVE-2021-30860)
A small utility to check the presence of known malicious payloads in PDF
files.
[2022-11-29T14:27:24Z INFO elegant_bouncer] Opening ../xpdf-
4.03/build/xpdf/forcedentry.pdf...
[2022-11-29T14:27:24Z INFO elegant_bouncer] Checking for JBIG2
presence... Present.
[2022-11-29T14:27:24Z INFO elegant_bouncer] CVE-2021-30860 vulnerability
trigger... Present.
To detect the presence of the FORCEDENTRY bug in an input file, we must first analyze it to check for the presence of a JBIG2 Object (analyze()
). If present, we must gather a list of JBIG2 Symbol Dictionary Segments (SDS) and their sizes. We (parse_jbig2_stream()
) must then locate a JBIG2 Text Region Segment and all its SDS references. Finally, we must count the total size of SDS referenced segments and compare it to the value of std::u32::MAX
to determine if there is an attempt to expoit the CVE-2021- 30860 bug.
Counting segment’s size
fn is_forcedentry(&self) -> bool {
for region in &self.regions {
let mut num_syms = 0;
if let Ok(refs) = region.get_refs() {
for ref_seg_num in refs {
let sz = self.get_len_by_seg_num(ref_seg_num as u32);
num_syms += sz;
}
}
if num_syms > std::u32::MAX as u64 {
return true;
}
}
false
}
Conclusion
The investigation of this bug demonstrates the importance of understanding exploitation techniques in
order to improve detection engineering. It highlights the need for security mitigations such as BlastDoor
sandbox systems to constantly evolve, as well as how attackers are often a few steps ahead of defenders.
Memory safe languages, hardware mitigations such as Memory Tagging Extensions (MTE), and
architectures like CHERI, should become more widely used to prevent such attacks on consumer devices.
The source-code of the ELEGANTBOUNCER tool can be found on GitHub.
Acknowledgments
I would like to thank Jeff for helping me understand this bug, Valentina for suggesting this target, and Ian
Beer and Samuel Groß of Google Project Zero for their amazing write-up on the sample shared by Citizen
Lab with them.
References
- ELEGANTBOUNCER
- xpdf-v4.03
- Citizen Lab – NSO Group iMessage Zero-Click Exploit Captured in the Wild
- Google Project Zero – A deep dive into an NSO zero-click iMessage exploit: Remote Code Execution
- Google Project Zero – FORCEDENTRY: Sandbox Escape
- Jeffrey Hofmann POC for CVE-2021-30860 (Part 1) (Part 2)
- JBIG2 ISO/IEC 14492
Memory Analysis Is Critical for Incident Response
To learn more about memory analysis and how to successfully capture and analyze memory dumps using the Comae platform, check out Matt’s blog post: How To Conquer Memory Analysis for Incident Response, Threat Hunting and Compromise Assessment.