ZOOMSDAY

How A Security Found a Nation-State Vulnerability in Zoom in One Day

A critical vulnerability in Zoom, a platform used by 70% of the Fortune 100, discovered by publicly available frontier models, allows an attacker participating in a meeting a zero-click remote code execution on all meeting participants across all native clients. This research emphasizes the risk of weaponized AI and how vulnerable we are as an industry.

Executive Summary

  • Ⓐ Security, the Autonomous Offensive Security and Remediation Platform, discovered a critical flaw in Zoom that let an attacker take complete control of another user's device during a live call. No click, no download, and nothing required of the victim but being in the meeting. The vulnerability is present in every version of Zoom on every device and operating system: Windows, Mac, iPhone, Android and Linux. The vulnerability exists in all versions up to and including 7.0.5.
  • All Zoom Workplace clients on all supported platforms before version 7.1.5 and 7.0.6 using end-to-end encryption settings are still vulnerable. We recommend updating your clients to the latest version.
  • The entire operation, from finding the flaw to building a working exploit, was carried out by Ⓐ using fewer than 20 prompts on publicly available AI models in under 24 hours.
  • This class of capability would previously have only been available to nation-state threat actors, but the model requiring elite teams, months of effort, and weapons-grade budgets has collapsed. Today, a single researcher was able to develop a nation-state-level exploit in less than a day.
  • The vulnerability is a memory-corruption bug that exploits Zoom's annotation feature.
    Zoom annotation is built on a proprietary protocol — closed code with no public documentation or specification. Ⓐ Research was able to exploit the fact that every Zoom client automatically parses whatever it receives, sending a specially crafted message to corrupt the receiving client's memory and run code on it. The protocol opens a direct channel between a viewer and a sharer, letting the attacker target each participant individually.
  • The exploit enables attackers to either join or host a meeting, target any participant, and take over their machine with no required action from the victim and no visual cue indicating the compromise.
  • Once the nefarious code is running on the victim's device, the threat actor can quietly steal personal data, switch on the microphone or camera to spy on the target, or install other malicious software. In a large call, that's a room full of targets from a single message, with no safe seat in it.
  • Ⓐ Security collaborated with Zoom to address the issue. The company reported the vulnerability to Zoom in June 2026. Zoom acknowledged receipt and deployed client-side and server-side fixes.
  • The vulnerabilities are tracked as CVE-2026-53413, CVE-2026-53414, and CVE-2026-53415 with critical 9.0 CVSS:4.0 scores. Zoom has issued a security alert.

Intro

Ⓐ found a critical, nation-state-grade vulnerability in Zoom in a single working day using an AI agent and models anyone can access today.

Exploits like this one are weapons. Governments regulate their export. Criminal organizations pay millions for them. Acquiring one has always required nation-state infrastructure, elite teams, and months of work.

That barrier is gone.

AI security research has been building to this in steps: first solving CTF challenges, then finding bugs in public bug-bounty programs. That work was impressive, but it lived in controlled or open environments, targets with published scope and known rules. Zoom is neither. It's closed-source enterprise software with no public internals, the kind of target that defenders assume is protected by obscurity and scrutiny. Producing a working exploit here sits in the category nation-states spend months and millions to reach.

The flaw allowed an attacker to take control of a device during any live Zoom call, with no action required from the victim, no click, no download. It worked in both directions: a compromised presenter could reach every participant, and any participant could reach the presenter. It was confirmed on all platforms Zoom runs on: Windows, macOS, iOS, and Android.

This matters beyond Zoom. Zoom is a core infrastructure for 70% of the Fortune 100, most of the Fortune 500, and federal agencies. Additionally, it is the platform where millions meet their doctors, lawyers, and families. But the real finding isn't the bug. It's the speed. The barrier to producing this class of weapon has collapsed, and it won't come back.

We disclosed the vulnerability to Zoom privately; their team engaged quickly and shipped a fix before publication.

The takeaway for security leaders: defenses built for a world where these weapons were scarce no longer hold. The only durable response is to turn the same capability inward, testing your own environment continuously, before an adversary gets there.

Technical Deep-dive

Why Zoom, and where we started

Ⓐ's mission is to secure modern enterprises against weaponized AI.
That means testing more than the systems our customers own. It means testing the software they depend on and cannot audit.

Zoom is one of those tools. It is closed-source, publishes no protocol specifications, and is deployed nearly everywhere that matters.

We started from the Android client, version 7.0.4. The Android Package is a container, Dalvik bytecode plus 121 native shared libraries. The Java layer is mostly UI and IPC glue. Anything that touches untrusted bytes at speed lives in native code, so the native side was the target.
That is still far more surface than anyone can reverse by hand, so the first job was ranking it.

How we mapped the attack surface

PROMPT
I’ve Decompiled Zoom Android v7.0.4. Java in the dex, 121 native .so files, IDA available.
Map the attack surface end to end: enumerate the native libraries and what each does, then for every function reachable from a JNI entry point, score its exposure to dangerous sinks (memcpy/strcpy/sprintf families, computed-size allocators) weighted by severity, function size and call depth.
Give me a ranked work queue plus a one-line 'library → entry point → sink class' map.

To find the targeted library our agents built a static pre-ranker that automatically scored every function reachable from a JNI entry point by its calls to dangerous sinks (memcpy/strcpy/sprintf, computed-size allocators), weighted by CWE severity, function size and call depth. It produced a ranked work queue of 3,762 functions across 70 libraries, topped by the video, image and audio stacks

That queue told us where Java could reach dangerous code and that turned out to be the wrong question. Its top entries were local paths: frame-capture and render entry points driven by the device's own camera, not by another participant. The one bug we did find on that side of the house, a 32-bit width × height overflow in a native video receive worker, we reached it sits behind a network thread with no JNI entry point at all so a JNI-rooted ranker was structurally blind to it. And it went nowhere: it turned out to be unreachable with the input shapes a real meeting produces.

That surface came up dry, so we flipped the search: instead of hunting for dangerous code, we hunted for what a remote participant can actually reach. That put the focus on the protocol itself — the messages clients send and receive from each other during a meeting, and how much of each one the sender controls. To find it we traced the client dynamically through a Frida MCP, exercising each meeting feature in a live call while watching which libraries loaded and which functions fired underneath. Annotation, a collaboration feature that lets you draw, type text, add shapes, or place stamps directly on a shared screen or digital whiteboard during a meeting, was the one that paid off: drawing an object lit up libannotate.so, and with it the serialize and deserialize routines that build the message on the sender and rebuild it on the receiver. It's also worth noting that the feature can be exploited whether the user is working with the tool in a particular meeting or not. It is “always on” and in older versions of the client “always vulnerable”

That put libannotate.so at the front. The static score had ranked it #45. It sits directly on the protocol that's reachable from remote — a proprietary, non-CVE library with complex messages that are built and sent over the wire and received on the other user's client. Also worth mentioning: the same source compiles into all platform clients, so a bug here isn't one-platform, it runs across the entire product line.

Reverse engineering the Annotation library

PROMPT
Reverse the annotation protocol in IDA.
I want the full opcode → handler map: which message types construct objects, which carry a full body vs. an id-only reference, how objects are created / synced / removed / full-synced, and where each handler forwards the deserialized object.
Rename handlers in the IDB as you go.

Annotation objects

The first thing to understand is that nothing about a drawing travels as an image.
When a participant draws on a shared screen or whiteboard, their client doesn't ship pixels, it builds a typed in-memory object describing the mark.
A freehand stroke becomes a CAnnoObjSmoothPen; a text box becomes a CAnnoObjTextbox; shapes, highlights, and arrows each have their own class.
Every one of them descends from a common base, CAnnoObj, which carries the shared header that contains object type, flags, geometry. Each subclass adds its own fields afterwards.

To leave the machine, that object is serialized: flattened into a byte stream and wrapped in a Protocol Data Unit (CAnnoPduAddObj and its siblings: add, update, remove, clear).
The PDU is what actually crosses the network.
On the far side the process runs in reverse — the receiver deserializes the bytes back into the same object graph and hands it to the renderer. Serialize and deserialize are mirror images of each other, and that symmetry is exactly what made the protocol recoverable: every field written by a stream_write on the send side has a matching stream_read on the receive side, so walking the caller sets of those two primitives reconstructs the whole grammar, field by field.

The format itself is self-describing in the worst way for a defender: it is a flat sequence of length-prefixed and count-prefixed reads. The parser reads a value off the wire, then trusts that value to decide how much more to read, how many styled runs a text frame contains, how many code units a glyph buffer holds. Those counts come from the sender.

How a drawing reaches every participant

Annotation PDUs don't go peer-to-peer. They are handed to Zoom's MMR (Multimedia Router) server and MMR doesn't blind-fan one client's object out to the whole room, every participant holds a channel to the sharer, and the sharer holds one back to each participant.


Upstream, participant → sharer carries the objects a viewer draws.
Downstream, sharer → participant carries the sharer's replies on that same channel, DocRequestDrawAck, AddObjAck and the receiving client runs them through the full deserialize stack.

Nothing on the receiving side enforces that the downstream channel carries only acknowledgements. CAnnoPduFactory::create reads the opcode off the wire and dispatches to that class's deserializer; on the paths traced here there is no sender-role or origin check. AddObj is 0x10001 and AddObjAck is 0x10002. One increment separates "here is an object" from "I received your object," and the factory cannot tell which seat the sender occupied.
Put 0x10001 and a serialized object graph where an ack body belongs and the victim reconstructs the object to full depth.
That is the design bug: an ack is allowed to carry an object body, and the receiver deserializes it.

The sharer seat is the stronger position, and the asymmetry is reach.
From it you hold a direct, addressed channel to every participant, so you can drive one victim's parser at a time.
From a viewer seat the same primitive exists but points at exactly one deserializer, the sharer's.

The Vulnerabilities

PROMPT
The deserialize side is remotely reachable — any participant's PDU gets rebuilt on the receiving client. That's the attack surface, so scope everything to those functions. Audit libannotate's object deserializers for memory-safety bugs: wire counts used as sizes, copies with no destination bound, allocations that don't match the bytes actually read.

RESULT
Strongest hit: CAnnoFormatBlock::Deserialize — unbounded copy into a fixed buffer.The function holds four fixed 128-byte buffers. For each one it reads a 32-bit count off the wire and copies 2 × count bytes in. The count is checked only for being non-zero — there is no comparison against the 128-byte destination anywhere in the function, and the copy routine it calls measures only the source, handing over as many bytes as the packet still has left. Neither side is guarding the destination.
The buffer being filled is the last field in the object, and that object is a member of the 704-byte text range that CAnnoTextFrame::Deserialize builds as a local variable in its own stack frame. So an oversized count does not stop at the end of the object — it keeps writing forward through the frame, into the saved registers and the return address.
Both halves of the write are attacker-supplied: the count sets how far it runs, and the bytes copied are the packet's own. This is a remotely triggerable stack buffer overflow.
PROMPT
Build the PDU that reaches it through normal parsing. No shortcuts - walk every field the parser expects: the factory type for the object, the flag bit that selects the text-frame body, the text-range header, then the oversized channel count and the overflow payload. Give me the byte layout, and deliver it over the real transport from one client to another. The victim's own parser does the work, no instrumentation on that side.

RESULT
745-byte AddObj PDU: opcode 0x10001, object flags bit 3 (TextFrame), fourth channel count 0x100. Delivered from a viewer to the presenter over Zoom's own encrypted transport, with nothing attached to the victim:
[F8]
HIJACKED DocRequestDrawAck serialize -> emitted 745B F8 payload (cap 128B -> 745B)
[drain]
sub_F9DE4 ran


XX-XX XX:XX:XX.XXX 13845 13845 F libc : stack corruption detected (-fstack-protector)

CVE-2026-53413: The overwrite

Both bugs live in the same place: the text annotation. It is a nested object in the protocol, and that is what makes it the richest target.

A text annotation is built in four layers and the receiver rebuilds every one of them from the wire. The CAnnoObj starts with the header which holds a flag bit that describes the object type, value 3 means "this object has a text body" and the parser follows it into a CAnnoTextFrame.

The text frame holds a list of CAnnoTextRange items — one per styled run of text. Each run carries its characters and its formatting, and the formatting lives in a CAnnoFormatBlock: a small header followed by four fixed 128-byte buffers, each holding a UTF-16 string such as a font name. 128 bytes is 64 characters. The wire supplies a character count per buffer, and the parser copies 2 × count bytes into it.

A run can hold a format block in either of two places. Normally it is a member of the range itself, and the range is a 704-byte local variable inside the text frame's deserializer — so it lives on the stack. But a run can also declare an extension: a single gate byte on the wire that, when non-zero, makes the receiver allocate a separate 592-byte child object on the heap, carrying its own format block.

Each count is a 32-bit value taken straight off the wire, and each buffer is a fixed 128 bytes. There is no bounds check to stop a count from exceeding the buffer length. The 4th buffer is the last buffer, so an oversized fourth count writes past the end of whatever object holds it: the 704-byte range on the stack, or the 592-byte child on the heap.

Stream_Read - this function is the write primitve that gets the wire input stream, the dst buffer and the wire size and copy to the buffer :

CAnnoTextFrame::Deserialize

CAnnoFormatBlock : buf4 is the last field, so an oversized count4 runs off the end of the object:

CAnnoExtBlock , CAnnoExtChild deserializers (the object allocation on the heap):

CVE-2026-53414 : The over-read

A separate bug - the glyph buffer is allocated from a wire count but filled from the packet: the receiver reads a character count, allocates 2 × count + 2 bytes without zeroing them, then copies in however many bytes actually arrived. Send a large count with a short body and the untouched tail is live receiver heap, with a terminating NUL written only at the far end. Confirmed live on the victim's own client. One captured slab held live code and vtable pointers from a single loaded library, alongside resource strings and rendering data - module-base material of exactly the kind an ASLR bypass needs.

Exploitation: Achieving RCE on MacOS

PROMPT
I control PC, X19–X28, X29/X30, and a contiguous stack, with no PAC or canary on this bundle. Find a one-shot shared-cache gadget that turns my controlled registers into a real call with an argument I control. Then prove it executes to completion with a visible effect on the victim, not just a crash or a hung branch

The macOS annoter bundle ships as plain arm64 with neither PAC nor stack canary.
So the overflow runs clean into the function's epilogue, which restores a batch of registers from the stack and then returns wherever the stack points.
We just overwrote that stack region, so it restores our values and jumps to our address.
One PDU hands us the program counter (PC) plus the callee-saved registers X19-X28 and the frame/link pair X29/X30, every one of them loaded straight from our overflow bytes. That is complete control.

With no PAC to authenticate anything, we don't need a complex exploit. We can just point the return address (X30/LR) at one ready-made instruction sequence (a "gadget") in the system's shared library cache, and let the epilogue pre-load the argument registers for us.
The overwrite plants three things, each landing in the register the gadget uses:

Register What we plant What it does
LR (X30) gadget: MOV X0,X19; MOV X1,X21; BL execvp redirects into execvp
X19 /Applications/Safari.app/Contents/MacOS/Safari becomes arg 1 — what to run
X21 the argument list {path, NULL} becomes arg 2 — how to run it

Because we control X19 and X21 directly, the gadget just copies them into X0/X1 and calls, no need to find the values anywhere else. The gadget, the path string, and the argument list all live in the same shared cache. macOS shifts that cache by a random amount each boot (ASLR), but shifts it as one block so a single leaked pointer rebases all three at once.

On the hijacked return, execvp("…/Safari", argv) runs and the victim's zoom.us process is replaced by the launched app. Fired live against a real target, it popped Safari open on the victim's Mac.

Leak-free control-flow hijack via heap shaping on Android

PROMPT
My overflow now runs off a heap chunk in a fixed size class instead of the stack.
I have no address knowledge though. can I get control purely through heap shaping, no leak? Spray the same size class to place an object I control adjacent to the overflow source, then corrupt only the low byte of its vtable pointer so the virtual call retargets to an offset inside the module regardless of ASLR slide. Find the un-gated op that fires that vcall, and prove it hijacks PC on an uninstrumented victim from the tombstone alone — layout doing the work an info-leak normally would.

An alternative path to leveraging the over-read vulnerability is a classic heap spray.
By spraying arbitrary data objects and partially corrupting their vtable pointers using the heap overflow primitive an attacker can gain controlled code execution.

The same overflow, using the heap version ExtChild (new(0x250), the 592-byte size class), gives us a controlled write past the end of a chunk. This can lead to code execution but requires some shaping.

Shape the heap first. Every ExtChild lands in the same 592-byte size class, so spraying them over AddObj lays them out back-to-back. This lets us place an object we control directly after our overflow source. Now the overflow doesn't spill into whatever happened to be there; it spills into a neighbor whose layout we know exactly. We size it to reach just the neighbor's first field: its C++ vtable pointer.

We can partially overwrite the pointer as our overflow is controlled. A vtable's low bytes are fixed relative to the module base no matter where ASLR loaded the module, so a partial overwrite retargets the virtual call to a chosen offset inside the module with zero address knowledge.

Triggering the bug. The corrupted pointer dispatches through an un-gated teardown path: normal wire ops (RemoveObj / ModifyObj) run a destructor that calls through the child's vtable. It triggers on any participant, viewer or presenter.

Demo Video

CVE-2026-53415: The Sequel - A Second, Independent RCE

Our work on Zoom's annotation engine didn't end with the first bug. The same AI-assisted research surfaced a second, independent flaw in the very same engine.
Zoom's parser handles a message type internally numbered 75 - an "auto-shape" metadata object - CAnnoObjAutoMetaShape - by reading attacker-supplied bytes directly into an internal linked-list structure and then unlinking that list, without ever validating the pointers it was handed. Lidor Elias identified and triaged this bug.
The result is a write-what-where primitive: the attacker controls both the value written and the address where it lands, providing a potential path to remote code execution. When shared the bug upon triage, Zoom already knew about it and had deployed a server-side mitigation that filters malicious messages (both to this CVE and the previous) before it reaches clients, but that defense carries the same gap as before: it cannot apply to end-to-end encrypted (E2EE) meetings, where the server is deliberately blind to call content. On E2EE calls, the crafted message passed through untouched and the vulnerability remained fully exploitable. Zoom closed it properly on the client side in version 7.1.5.

Disclosure

Ⓐ Research identified this vulnerability on June 8, 2026, and confirmed a working zero-click RCE against Zoom Client v7.0.5 across all platforms the following day. We reported it to Zoom on June 10, and Zoom acknowledged receipt within a day. Their response was fast and thorough: a client-side fix shipped in v7.1.0 just twelve days after our report, and Zoom followed up with a server-side mitigation on July 15 to protect users still running earlier client versions before they could update.

Because a zero-click RCE requires no user interaction, we prioritized giving customers time to receive both the client patch and the server-side mitigation before publishing. This post follows that coordinated timeline, and we are releasing it alongside CVE assignment.

2026-06-08 - Ⓐ Research discovers the Zoom annotation memory-corruption vulnerability.

2026-06-09 - Zero-click RCE confirmed on Zoom client v7.0.5 (all platforms).

2026-06-10 - Ⓐ Research reports the vulnerability to Zoom.

2026-06-11 - Zoom acknowledges receipt.

2026-06-22 - Zoom deploys client-side fix in v7.1.0 including patch for CVE-2026-53413,CVE-2026-53414.

2026-07-15 - Zoom deploys server-side mitigation for earlier versions.

2026-07-20 - Zoom deploys client-side fix in v7.1.5 including patch for CVE-2026-53415.

2026-08-11 - Public disclosure.

Guidance for mitigation

Zoom's bulletin ZSB-26015, ZSB-26016 & ZSB-26017 lists the affected products:

  • Zoom Workplace, all supported platforms, before version 7.1.5 and 7.0.6 in their respective branches
  • Zoom Workplace VDI Client for Windows, before versions 7.0.11 and 6.6.16 in their respective branches
  • Zoom Rooms, all supported platforms, before version 7.1.0
  • Zoom Meeting SDK, all supported platforms, before version 7.1.0

Updating closes these CVEs. Until every client is updated, check that end-to-end encryption setting is off. Since Zoom's server-side mitigation on July 15, malicious annotation messages are filtered before they reach clients, and enhanced encryption, Zoom's default, leaves that filter in place. E2EE removes it, because a server cannot inspect what it cannot read. E2EE would not have stopped this attack anyway: the attacker sits in the meeting, holding the keys. It has its own uses, just not this one.

Disable end-to-end encryption to protect older, vulnerable clients

Conclusion & Impact analysis

One malformed annotation, drawn by anyone in the call, was enough to take over every other device in the room. The same source compiles into the Windows, macOS, iOS, and Android clients.

But the vulnerability is not the story. The story is what it cost to find it.

This is closed-source enterprise software with no published protocol, the class of target defenders assume is safe because it is large, scrutinized, and opaque.
Producing a working exploit against it has always been nation-state work: elite teams, months of effort, budgets that governments regulate as weapons.
Ⓐ did it in a single day, with an AI agent and models anyone can access today.

That is the shift security leaders have to price in. The barrier that kept these weapons scarce has collapsed, and it will not come back. Attackers already have this capability.

The only open question is whether defenders reach their own exposures first. The durable response is to turn the same offensive capability inward and run it against your own environment continuously, before someone else does.

Share this article
About the author
Idan Levcovich has spent more than 20 years building offensive cybersecurity capabilities, focused on attack methods and vulnerability research. With a background in Israeli intelligence, he has led offensive research and exploitation work across multiple security companies.