TTPwire Vol. 1 · MITRE ATT&CK·Tagged

← All stories

Google Project Zero

A 0-click exploit chain for the Pixel 9 Part 1: Decoding Dolby

Natalie Silvanovich · 2026-01-14 · Read original ↗

ATT&CK techniques detected

48 predictions
T1055.001Dynamic-link Library Injection
99%
“than 0x63c30 relative to the static buffer ’ s base! calling controllable functions now that i had the ability to perform multiple writes to the static buffer, it was time to figure out a path to shellcode execution. this required analyzing how the function pointers in the static…”
T1055.001Dynamic-link Library Injection
98%
“##utable. selinux prevented both of these. it turns out the mediacodec selinux context does not have any allow rule that allows it to open and write the same file, so dlopen was a non - starter. additionally, mediacodec does not have execmem permissions, so making memory executab…”
T1055.001Dynamic-link Library Injection
97%
“indirect function call using dlb _ forwardmodulationcomplex, if there happens to be a situation where a pointer to a function pointer is available instead of the function pointer itself. finally, the call is repeated a specific number of times, based on a controllable value read …”
T1055.001Dynamic-link Library Injection
97%
“that the samsung s24 enforces this policy on its media decoding processes. however, this was somehow left out of the pixel 9. a seccomp policy similar to samsung ’ s would have prevented the call to pwrite used by the exploit. this wouldn ’ t have prevented exploitation, as every…”
T1055.001Dynamic-link Library Injection
97%
“the time when the process is running normally. another question is whether the exploit could be made more reliable. i have two ideas in this regard, both which would require substantial development effort. to remove the 1 / 16 probability when guessing the dynamic buffer location…”
T1055.001Dynamic-link Library Injection
96%
“static _ buffer [ 1 ], static _ buffer [ 7 ], in _ param ) ; result = dlb _ forwardmodulationcomplex ( param _ x0, index _ val, param _ val, * static _ buffer, static _ buffer [ 13 ], static _ buffer [ 8 ], static _ buffer [ 9 ] ) ; index = * ( unsigned int * ) static _ buffer ; …”
T1055.001Dynamic-link Library Injection
96%
“value the exploit guesses this nibble to be. still, this is only about 5 % of the 1. 3 mb of code in libcodec2 _ soft _ ddpdec. so. for the indirect call, 0xffff spans almost the entire export table, as well as the global offset table ( got ), so there ’ s some options there, but…”
T1055.001Dynamic-link Library Injection
95%
“the indirect call to the fopen pointer in the got, and call it several times on / proc / self / mem change the indirect call to memcpy, and copy the fopen got entry to the dynamic buffer set the dst parameter of memcpy to the location of the got pointer in the dynamic buffer and …”
T1055.001Dynamic-link Library Injection
95%
“investigating the mediacodec context ’ s permissions, we came up with the following strategy : map shellcode into memory using write dynamic call fopen on / proc / self / mem many times, so a file descriptor number associated with / proc / self / mem can be easily guessed call pw…”
T1055.012Process Hollowing
94%
“are written with write dynamic, they are overwritten before they can be used to set payload _ extra and nearby fields. i tried stopping the write from happening by including erroneous data in the syncframe that prevented baps from being written, but this also stopped emdf data fr…”
T1068Exploitation for Privilege Escalation
93%
“a driver accessible from the sandbox the decoder runs in on a pixel 9, and reported cve - 2025 - 36934. as i ’ ve shared this research, vendors as well as members of the security community have questioned whether such vulnerabilities are exploitable, as well as whether 0 - click …”
T1574.001DLL
90%
“##2 this syncframe writes direct _ call _ x2 with the integer length of the shellcode. 84 _ write _ end _ special and 86 _ write _ start _ special these syncframes copy the pointer to pwrite to the direct _ call _ fptr ( entry 14 ), using the same method as other pointer copies f…”
T1055.001Dynamic-link Library Injection
79%
“50 _ write _ x3 _ special though it wasn ’ t strictly necessary at this point, i wanted to set direct _ call _ x3 to strstr, so it would be available as offset, the fourth parameter to the eventual pwrite call. this made sense because the pointer was currently available in the dy…”
T1574.013KernelCallbackTable
74%
“##f _ analysisl is laid out as follows : the function pointer for the direct call is available to be overwritten, as are its parameters, arm64 registers x0 through x3. the indirect function parameters are also calculated from values in this structure, which i will explain in more…”
T1055.001Dynamic-link Library Injection
73%
“write _ x1 _ end _ special and 58 _ write _ x1 _ start _ special when starting this exploit, i genuinely believed it would be possible to get shellcode execution without write dynamic once write static was unlocked. this turned out to be wrong. in the plan i wrote up for the expl…”
T1574.013KernelCallbackTable
71%
“to the string ‘ wb ’. 14 _ write _ fopen this syncframe partially overwrites entry 9, so the indirect function pointer now references fopen. fopen will immediately be called four times, the default value of loop _ count. 16 _ garbage to 23 _ garbage the exploit now processes a fe…”
T1611Escape to Host
69%
“##x50, causing the dynamic buffer to be reallocated at dynamic _ base + 0x5000 when third. mp4 is loaded third. mp4 - - contains the rest of the exploit note that dynamic _ base is the location of the dynamic buffer with the lower two bytes cleared, i. e. dynamic _ buffer & & 0xf…”
T1055.001Dynamic-link Library Injection
69%
“be processed. the real challenge here would be copying from the static buffer to the dynamic buffer without guessing the library location, as both the direct and indirect calls available are quite limited. but if this was possible, the unreliability due to not knowing the library…”
T1055.001Dynamic-link Library Injection
68%
“##e90 and pwrite is 0xdd6c0. a one - byte overwrite could change a fopen pointer to 0x92e4a, then : 0x157 × 890 + 0x92e4a = 0xdd6c0 another question is whether this math would work generally, even on devices that have libc compiled with different offsets. i believe it would. in e…”
T1027.001Binary Padding
66%
“allows payload _ extra bytes to be allocated in the static buffer, but not overwritten by the payload. since the length and contents of payload data is controllable by the attacker, it would be possible to write basically any data at any relative location in the static buffer if …”
T1611Escape to Host
66%
“why does this cause my attempt to overwrite the function pointer in the static buffer to fail? when the decoder processes the emdf container with a large number of payloads, it starts to allocate memory outside of the evo heap, because the heap length was overwritten with a very …”
T1059.006Python
66%
“to be fairly complicated, with some unexpected problems. in order to explain these, this section will go through the third file of the exploit, one syncframe at a time. you can follow along here. note that filenames that begin with numbers, for example, 10 _ write _ x0 contain th…”
T1055.001Dynamic-link Library Injection
66%
“0xffff due to the write dynamic primitive. but since payloads use skip buffer memory at a ratio of 19 bits to 90 bytes, the function pointer could theoretically be overwritten using 0x286e8 / 90 * 19 / 8 = ~ 0xa000 bytes of skip data, which is smaller than the available 0xffff by…”
T1055.001Dynamic-link Library Injection
65%
“it might be possible to use this write as well. this strategy would not make determining the dynamic buffer allocation 100 % reliable, because the location where the dynamic buffer is reallocated needs to be mapped. for example, if an allocation at dynamic _ base + 0x3000 has its…”
T1574.013KernelCallbackTable
62%
“memset. based on its parameters, this call is obviously intended to zero the evo heap, but since the heap length is now larger than the static buffer, it writes out of bounds. i performed a minimal analysis of what triggers this call and discovered that it requires processing two…”
T1055.001Dynamic-link Library Injection
60%
“the android version of the library ). the second is an indirect call to dlb _ r8 _ fft _ 64 via a pointer at offset 0x2a7b60. the upper nibble of the second byte of where these functions are loaded is randomized by aslr on android. i tested this, and saw the behavior below, which…”
T1574.001DLL
55%
“is a multiple of 0x157 away from pwrite. the final syncframe moves the skip pointer to another address so it doesn ’ t write the byte a second time. 66 _ write _ index the exploit is about to call the increment gadget a large number of times, which will also increment the variabl…”
T1574.013KernelCallbackTable
53%
“is a multiple of 0x157 away from pwrite. the final syncframe moves the skip pointer to another address so it doesn ’ t write the byte a second time. 66 _ write _ index the exploit is about to call the increment gadget a large number of times, which will also increment the variabl…”
T1055.001Dynamic-link Library Injection
51%
“the ‘ static buffer ’. this buffer contains a large struct, which supports all the functionality of the decoder. the evo heap is part of this buffer. on android, the size of the static buffer is 692855. another buffer that is allocated is the ‘ dynamic buffer ’. this buffer is us…”
T1055.001Dynamic-link Library Injection
49%
“to the string ‘ wb ’. 14 _ write _ fopen this syncframe partially overwrites entry 9, so the indirect function pointer now references fopen. fopen will immediately be called four times, the default value of loop _ count. 16 _ garbage to 23 _ garbage the exploit now processes a fe…”
T1574.013KernelCallbackTable
45%
“##mcpy to copy a different function pointer, and after some testing, i selected _ _ stack _ chk _ fail, as it doesn ’ t get called during normal operation and the functions after it in libc aren ’ t used by the udc either. so this combination of syncframes uses the same trick as …”
T1055.001Dynamic-link Library Injection
43%
“len ’ field of the dynamic buffer ’ s header, then overwrite it, so the chunk ’ s length is 0x17000 instead of 0x15000. then, when the decoder is reset ( i. e. when a new file is played ), the buffer will be reallocated, with an extra 0x2000 bytes of writable space before the hea…”
T1611Escape to Host
42%
“and the offset corresponding to payload _ extra to 0x28530. it then sets the skip pointer to dynamic _ base + 0x473a. 5 _ do _ heap _ write this syncframe writes the start of an emdf container to the address set in the previous frame, so that the data written by 3 _ adjust _ writ…”
T1574.013KernelCallbackTable
42%
“so that data doesn ’ t get corrupted by a future skip pointer write. i will call this primitive write dynamic fast. there ’ s two downsides of this primitive compared to write dynamic. one is that since the emdf container that moves the skip pointer and the data written are in th…”
T1574.001DLL
42%
“len ’ field of the dynamic buffer ’ s header, then overwrite it, so the chunk ’ s length is 0x17000 instead of 0x15000. then, when the decoder is reset ( i. e. when a new file is played ), the buffer will be reallocated, with an extra 0x2000 bytes of writable space before the hea…”
T1055.003Thread Execution Hijacking
42%
“write _ x1 _ end _ special and 58 _ write _ x1 _ start _ special when starting this exploit, i genuinely believed it would be possible to get shellcode execution without write dynamic once write static was unlocked. this turned out to be wrong. in the plan i wrote up for the expl…”
T1574.013KernelCallbackTable
41%
“##c on initialization, and no other static or dynamic buffers. the following diagram shows where the skip buffer and evo heap are located in relation to these buffers : the evo heap is located at offset 0x61d28 in the static buffer, and immediately afterwards is the pointer used …”
T1055.001Dynamic-link Library Injection
39%
“so that data doesn ’ t get corrupted by a future skip pointer write. i will call this primitive write dynamic fast. there ’ s two downsides of this primitive compared to write dynamic. one is that since the emdf container that moves the skip pointer and the data written are in th…”
T1574.013KernelCallbackTable
39%
“write, the skip pointer needs to be set to 0x000000xxxxxxz065, where z is one less than y. this means the exploit needs to overwrite the nibble y, and therefore know the value of y, which is randomized by aslr. i did an experiment on a pixel to see how this value was randomized a…”
T1055.001Dynamic-link Library Injection
38%
“pointer to the increment gadget, which is then called 0xf6 times, causing the function pointer in the dynamic buffer to point to pwrite. 74 _ set _ pc _ back sets the direct call pointer back to the ret gadget, so incrementing stops. 76 _ set _ malloc the indirect function pointe…”
T1574.001DLL
38%
“pointer to the increment gadget, which is then called 0xf6 times, causing the function pointer in the dynamic buffer to point to pwrite. 74 _ set _ pc _ back sets the direct call pointer back to the ret gadget, so incrementing stops. 76 _ set _ malloc the indirect function pointe…”
T1611Escape to Host
37%
“of memory in the dynamic buffer that the exploit needs, but if that ’ s possible, it could potentially remove the unreliability caused by the second nibble randomization of the dynamic buffer. to remove the 1 / 16 probability when guessing the load address of libcodec2 _ soft _ d…”
T1055.001Dynamic-link Library Injection
34%
“##tor will be at the 0x5000 address, but the scudo header at 0x3000 will not be cleared. this only works because the dynamic buffer is the only buffer anywhere near its size that is allocated by the process, otherwise, there would be a risk that this buffer would be allocated aga…”
T1204.002Malicious File
33%
“##tor will be at the 0x5000 address, but the scudo header at 0x3000 will not be cleared. this only works because the dynamic buffer is the only buffer anywhere near its size that is allocated by the process, otherwise, there would be a risk that this buffer would be allocated aga…”
T1055.012Process Hollowing
32%
“##tor will be at the 0x5000 address, but the scudo header at 0x3000 will not be cleared. this only works because the dynamic buffer is the only buffer anywhere near its size that is allocated by the process, otherwise, there would be a risk that this buffer would be allocated aga…”
T1055.001Dynamic-link Library Injection
32%
“, even if i didn ’ t have an immediate strategy for overwriting it. analyzing the static buffer, i learned that my options were fairly limited. there are only two function pointers in the entire static buffer, called very frequently by the function dlb _ clqmf _ analysisl, at off…”
T1574.013KernelCallbackTable
32%
“. meanwhile, every emdf payload processed causes 96 bytes to be allocated on the evo heap. this means it would take roughly 99 payloads to fill up the evo heap, which translates to 235 bytes of skip data. this is well within the available skip data space. using this technique, it…”
T1055.001Dynamic-link Library Injection
31%
“m going to skip over some details here, because i ended up not using this strategy in the final exploit, but by writing data to the altered skip pointer, it was possible to overwrite the function pointer, which demonstrated that this vulnerability could set the program counter! n…”

Summary

Over the past few years, several AI-powered features have been added to mobile phones that allow users to better search and understand their messages. One effect of this change is increased 0-click attack surface, as efficient analysis often requires message media to be decoded before the message is opened by the user. One such feature is audio transcription. Incoming SMS and RCS audio attachments received by Google Messages are now automatically decoded with no user interaction. As a result, audio decoders are now in the 0-click attack surface of most Android phones. I’ve spent a fair bit of time investigating these decoders, first reporting CVE-2025-49415 in the Monkey’s Audio codec on Samsung devices. Based on this research, the team reviewed the Dolby Unified Decoder, and Ivan Fratric and I reported CVE-2025-54957. This vulnerability is likely in the 0-click attack surface of most Android devices in use today. In parallel, Seth Jenkins investigated a driver accessible from the sandbox the decoder runs in on a Pixel 9, and reported CVE-2025-36934.