There are new articles available, click to refresh the page.
Before yesterdayZecOps Blog

Fake Droids: Your New Android Device is Actually an Old Android 6

16 August 2022 at 16:08
Fake Droids: Your New Android Device is Actually an Old Android 6

During a Digital Forensics investigation, ZecOps made an interesting finding: a cheap burner device that purported to be an Android 10 was actually an old Android 6.

In the first part of the series, we presented how attackers can ‘fake’ the shutdown screen on iOS to achieve persistence. 

Now, we demonstrate how device-fakers sell old Android devices as a new device, with fake specifications including faking of the CPU speed, Android version, Patch level, Memory, and even screen resolution.

It all started during a Digital Forensics investigation of a phone that was purchased from AliExpress as a cheap burner device was not how it presented itself. 

Cheap Android 10 Phone?

Following a brief investigation, we were able to locate the phone below on AliExpress. Shortly after the delivery, we were able to confirm our findings.

The link to the device we purchased is here. The phone looks stunning and is very cheap! What possibly can go wrong?

After inspecting the phone with ZecOps for Mobile, the problem became clear: the device was recognized by our system as an Android 6, not an Android 10.

Wait, what ?

First thing we did was go to Settings→ About phone. We found:

As you can see, the device shows that it is an Android 10 with 10 processor cores. We then used a known software called “CPU-Z”. This app is used to check the hardware properties.

Deeper Dive Into The World of Fake Android Devices

We checked the kernel version and device properties:

According to the output, the kernel version is 3.18.19 and Android version is 6.0. The Settings app, as well as CPU-Z, is trying to fool the end-user (but not ZecOps!). 

Let’s check the processor:

Here we see MT6753. This processor has 8 cores, instead of 10 cores, as was displayed in the Settings app.

Let’s check the API version:

The API version corresponds with Android 6 – consistent with previous findings.

Furthermore, we’ve made several observations about the UI of this phone, which is similar to Android 6 variants we previously used.

Examining Where the Android Faking Happens

Now we know that ZecOps was correct and that the phone is fake. But we can’t stop here – let’s examine where the interception of the data happens and how deep this device-faking goes:

We do know that the Settings app and CPU-Z app are reporting the same fake hardware details.

In order to understand where the data-faking happens, let’s focus on the Android version and check if all other apps do see the same device properties as CPU-Z and let’s do it in the same way CPU-Z or Settings app do.

First of all, let’s check if this fake is global, this will help us to guess the right component where the faking is happening. We already know that with ADB we can obtain the correct OS version number, which is consistent with both SDK version and the kernel version.

Let’s start with a simple check and establish a starting point: write our own program to check the actual version loaded with the precompiled framework.

The code (placed to onCreate of the basic project template):

        import android.os.Build;
        import android.util.Log;
        Log.e("TEST", Build.BOOTLOADER);
        Log.e("TEST", Build.VERSION.RELEASE);
        Log.e("TEST", Build.VERSION.BASE_OS);
        Log.e("TEST", Build.VERSION.CODENAME);

        Log.e("TEST", String.format("%d", Build.VERSION.SDK_INT));
        Log.e("TEST", String.format("%s", Build.BOARD));
        Log.e("TEST", String.format("%s", Build.FINGERPRINT));
        Log.e("TEST", String.format("%s", Build.HARDWARE));


2022-04-27 17:10:33.072 17287-17287/com.zecops.myapplication E/TEST: unknown
2022-04-27 17:10:33.072 17287-17287/com.zecops.myapplication E/TEST: 6.0
2022-04-27 17:10:33.072 17287-17287/com.zecops.myapplication E/TEST:
2022-04-27 17:10:33.072 17287-17287/com.zecops.myapplication E/TEST: REL
2022-04-27 17:10:33.073 17287-17287/com.zecops.myapplication E/TEST: 23
2022-04-27 17:10:33.073 17287-17287/com.zecops.myapplication E/TEST: unknown
2022-04-27 17:10:33.073 17287-17287/com.zecops.myapplication E/TEST: alps/full_hy6580_we_m/hy6580_we_m:6.0/MRA58K/1545278126:user/release-keys
2022-04-27 17:10:33.073 17287-17287/com.zecops.myapplication E/TEST: mt6735

So here everything is displayed correctly and consistently with adb output. We need to check the Settings app next.

Extracting and apktool’ing of the corresponding apk gives us the id of the android version string resource id, it is “firmware_version”, but no classes.dex inside. It’s OK, it is the usual situation for the Settings app.

After deodexing /system/priv-app/Settings/oat/arm/Settings.odex with baksmali and getting the code we can grep it for the firmware_version constant and see that it is taken from os.android.Build.VERSION

In DeviceInfoSettings.smali, and we’ll see something like this:

    const-string/jumbo v17, "firmware_version"
    sget-object v18, Landroid/os/Build$VERSION;->RELEASE:Ljava/lang/String;
    move-object/from16 v0, p0
    move-object/from16 v1, v17
    move-object/from16 v2, v18
    invoke-direct {v0, v1, v2}, Lcom/android/settings/DeviceInfoSettings;->setStringSummary(Ljava/lang/String;Ljava/lang/String;)V

This corresponds to the code in onCreate in the https://android.googlesource.com/platform/packages/apps/Settings/+/refs/tags/android-6.0.1_r55/src/com/android/settings/DeviceInfoSettings.java

However the line number in the debug information does not correspond to the exact line, but it is around the needed code. 

After decompiling the code back to java we can finally see something more interesting:

Now everything is clear. The code which is similar to original implementation is under condition that never actually happens, because the actual value of “persyst.sys.hdf.androidsdk” on this phone is 1.

There are funny nuances here, including the replacement of the Security Patch Level from 2018 to 2019 !

Now we know where the fake Android version came from: it comes from HdfUtil.GetHrfAndroidString, along with a lot of other fake properties.

Let’s further examine how the fake Android version is configured:

As we can see at the start of onCreate function SystemProperties.getInt(“persyst.sys.hdf.androidsdk”, 0) is called.

We can verify this value with getprop utility and see that the returned value is 1, so the value that will be displayed as android version comes from HdfUtil.getHrfAndroidString .

Below is the source of this function:

public static String getHrfAndroidString() {
        switch (SystemProperties.getInt("persist.sys.hdf.androidv", 0)) {
            case 0:
                return "9.1";
            case 1:
                return "6.0";
            case 2:
                return "6.1";
            case 3:
                return "8.0";
            case 4:
                return "8.1";
            case 5:
                return "9.0";
            case 6:
                return "9.1";
            case 7:
                return "10.0";
                return "6.0";

This function actually returns the value “10.0” according to the real value returned from SystemProperties.getInt(“persist.sys.hdf.androidv”, 0) which can be checked with getprop utility and equals 7. 

The similar exercise happens with all the other parameters.

Almost all the onCreate function of the DeviceInfoSettings class refers to this class, instead of doing what the original Android source says (which refers mostly to os.android.Build.VERSION class, see below)

All the HW-faking happens in HdfUtil.java. This file wasn’t obfuscated.

When we understand what exactly happened here, we can compare the real HW properties of this device with what our flattering Settings app says.  

Property Specs from site Settings and HdfUtil.java CPU-Z App Real values
Android 10.0 10.0 10.0 6.0 (getprop)
RAM 12GB 8GB 8192 MB 8388608 K
ROM 512G 512G 512G 8G (pushing files until no space left on the device)
CPU MTK6799 10 cores MTK6799 10 cores MTK6799 MTK6753(/proc/cpuinfo)
Screen resolution 2320×1280 2320×1280 N/A 720×1520 (wm size)
Security patch N/A January 5, 2019 January 5, 2019 January 5, 2019

So the mystery of the phone is partially solved:

  • The fakers made changes in the sources of existing Android 6 environment.
  • They compiled this from the Java source code – we see it due to valid debug information existing in the smali code.
  • They added some build parameters which we can see in /system/build.prop and the outputs via adb, which define what exactly the customized Settings app should show to the user. The original parameters remained intact, so all the untouched framework works fine.  
  • Number of HW variants in the HdfUtil.java shows that this framework was probably used for faking other phones too.  
  • We can see that this is enough to fool the undemanding user, searching for a cheap Android 10 burner/regular phone. 

There are three mysteries left to address:

  • How exactly did they fool the CPU-Z application, which was not installed on the phone when it arrived?
  • Are there other similar phones, who is responsible for faking the specs, and can we find these phones automatically?
  • Is there another malware on the phone allowing the sellers to obtain remote access to the device?

Fooling The CPU-Z Application

After spending a some time tracing how exactly the android.os.Build.VERSION works, rooting the device (mtk-su works perfectly), going down to reversing the framework and its native part, it appears that it was more important to focus on how they display the fake data rather than how they get the version number and string.

It appears that they simply changed the code of the class android.widget.TextView to make it display the required fake values in specific applications. Sometimes, things are more simple than they appear to be. 

In order to verify that we should extract boot.oat from the device, convert it to dex files with oat2dex utility, and then decompile the resulting dex files.   

This is how it looks (the following code is from public final void setText(CharSequence var1) ) :

The main idea behind it is the following: if the name of the package is com.cpuid.cpu_z (which corresponds to the CPU-Z package name) and the previous string that was set with function is one of the faked parameters, the text is magically changing to the value encoded in the way similar to that was used in the Settings application based on the same build parameters that can be inspected with getprop.

Similar code snippets related to the following packages also found in this code:

  • com.antutu.ABenchMark
  • com.mediatek.camera
  • com.mediatek.filemanager
  • com.qiku.android.filebrowser
  • com.finalwire.aida64
  • com.ludashi.benchmark
  • ru.andr7e.deviceinfohw

This increases the suspicion that not only CPU-Z was fooled, but also other common applications to check for device specs / device benchmark.

After further analyzing various interesting code pieces, we decompiled all the framework and surprisingly found yet another interesting finding that shares light on how the fakers deal with other benchmarking / specs applications.

It appears that there is some suspicious activity in other classes of the specs-faking framework, specifically in the Package Manager. 

After reversing the Package Manager, it appears that in addition to fooling these applications the specs-fakers also fooled the Package manager with an interesting approach: instead of installing the APKs that were downloaded from Google Play, they used pre-stored and tampered copies from /system/data . Following a brief analysis we concluded that these APKs are not malicious. This change was made to assure that the version of the fake apps that they were dealing with was properly tested and displayed fake values. 

Finally, the authors blocked crashes reports of the Google Play protect in activity manager, and modified the shutdown animation according to ro.hdf.shutdown.ani parameter. 

Preinstalled malware and what it does

We will keep this for the next blog in the series.

ZecOps Announces Support for Forensics Images Acquired by GrayShift

28 April 2022 at 10:53
ZecOps Announces Support for Forensics Images Acquired by GrayShift

ZecOps is pleased to announce native support of mobile forensic images acquired with Graykey. With the latest release, ZecOps is capable of digesting filesystem archives acquired by GrayKey, GrayShift’s flagship product, providing cybersecurity insights and automatic analysis for ZecOps customers.

ZecOps can automatically digest and analyze various data points that exist on the disk, including user-space crashes, kernel-space crashes, code-signing issues, stored events, filesystem IOCs.

In order to provide ideal results, it’s important to maintain the device tree structure.

Typically with computer forensics, it is correct to perform an extraction as a first step. With mobile devices, however, this is not the case. Due to the reliance on exploits and oftentimes the requirement to power off devices, ZecOps recommends:

  1. Leverage ZecOps Deep or Full collection modes. This will collect indicators of non-persistent malware first and wouldn’t be impacted by the exploits that forensics solutions leverage to operate.
  2. Perform an extraction using solutions like GrayKey.
  3. Import the extraction results into the ZecOps dashboard.

The ZecOps solution is capable of analyzing forensics files generated by the use of Cellebrite, GrayKey / Magnet AXIOM Forensics, and others. 

By digesting the information rapidly and pointing out  attacks, anomalies, and abnormal behavior, ZecOps accelerates investigations from months to minutes, saving valuable time.

To analyze iOS and Android devices for signs of attacks, with or without access to forensics extraction tools, contact ZecOps Sales.

Persistence without “Persistence”: Meet The Ultimate Persistence Bug – “NoReboot”

4 January 2022 at 20:49
Persistence without “Persistence”: Meet The Ultimate Persistence Bug – “NoReboot”

Mobile Attacker’s Mindset Series – Part II

Evaluating how attackers operate when there are no rules leads to discoveries of advanced detection and response mechanisms. ZecOps is proudly researching scenarios of attacks and sharing the information publicly for the benefit of all the mobile defenders out there.

iOs persistence is presumed to be the hardest bug to find. The attack surface is somewhat limited and constantly analyzed by Apple’s security teams.

Creativity is a key element of the hacker’s mindset. Persistence can be hard if the attackers play by the rules. As you may have guessed it already – attackers are not playing by the rules and everything is possible.

In part II of the Attacker’s Mindset blog we’ll go over the ultimate persistence bug: a bug that cannot be patched because it’s not exploiting any persistence bugs at all – only playing tricks with the human mind.

Meet “NoReboot”: The Ultimate Persistence Bug

We’ll dissect the iOS system and show how it’s possible to alter a shutdown event, tricking a user that got infected into thinking that the phone has been powered off, but in fact, it’s still running. The “NoReboot” approach simulates a real shutdown. The user cannot feel a difference between a real shutdown and a “fake shutdown”. There is no user-interface or any button feedback until the user turns the phone back “on”.

To demonstrate this technique, we’ll show a remote microphone & camera accessed after “turning off” the phone, and “persisting” when the phone will get back to a “powered on” state.

This blog can also be an excellent tutorial for anyone who may be interested in learning how to reverse engineer iOS.

Nowadays, many of us have tons of applications installed on our phones, and it is difficult to determine which among them is abusing our data and privacy. Constantly, our information is being collected, uploaded.

This story by Dan Goodin, speaks about an iOS malware discovered in-the-wild. One of the sentences in the article says: “The installed malware…can’t persist after a device reboot, … phones are disinfected as soon as they’re restarted.”.

The reality is actually a bit more complicated than that. As we will be able to demonstrate in this blog, we cannot, and should not, trust a “normal reboot”.

How Are We Supposed to Reboot iPhones?

According to Apple, a phone is rebooted by clicking on the Volume Down + Power button and dragging the slider.

Given that the iPhone has no internal fan and oftentimes it keeps its temperature cool, it’s not trivial to tell if our phones are running or not. For end-users, the most intuitive indicator that the phone is the feedback from the screen. We tap on the screen or click on the side button to wake up the screen.

Here is a list of physical feedback that constantly reminds us that the phone is powered on:

  • Ring/Sound from incoming calls and notifications
  • Touch feedback (3D touch)
  • Vibration (silent mode switch triggers a burst of vibration)
  • Screen
  • Camera indicator

“NoReboot”: Hijacking the Shutdown Event

Let’s see if we can disable all of the indicators above while keeping the phone with the trojan still running. Let’s start by hijacking the shutdown event, which involves injecting code into three daemons.

When you slide to power off, it is actually a system application /Applications/InCallService.app sending a shutdown signal to SpringBoard, which is a daemon that is responsible for the majority of the UI interaction.

We managed to hijack the signal by hooking the Objective-C method -[FBSSystemService shutdownWithOptions:]. Now instead of sending a shutdown signal to SpringBoard, it will notify both SpringBoard and backboardd to trigger the code we injected into them.

In backboardd, we will hide the spinning wheel animation, which automatically appears when SpringBoard stops running, the magic spell which does that is [[BKSDefaults localDefaults]setHideAppleLogoOnLaunch:1]. Then we make SpringBoard exit and block it from launching again. Because SpringBoard is responsible for responding to user behavior and interaction, without it, the device looks and feels as if it is not powered on. which is the perfect disguise for the purpose of mimicking a fake poweroff.

Example of SpringBoard respond to user’s interaction: Detects the long press action and evokes Siri

Despite that we disabled all physical feedback, the phone still remains fully functional and is capable of maintaining an active internet connection. The malicious actor could remotely manipulate the phone in a blatant way without worrying about being caught because the user is tricked into thinking that the phone is off, either being turned off by the victim or by malicious actors using “low battery” as an excuse. 

Later we will demonstrate eavesdropping through cam & mic while the phone is “off”. In reality, malicious actors can do anything the end-user can do and more. 

System Boot In Disguise

Now the user wants to turn the phone back on. The system boot animation with Apple’s logo can convince the end-user to believe that the phone has been turned off. 

When SpringBoard is not on duty, backboardd is in charge of the screen. According to the description we found on theiphonewiki regarding backboardd.

Ref: https://www.theiphonewiki.com/wiki/Backboardd

“All touch events are first processed by this daemon, then translated and relayed to the iOS application in the foreground”. We found this statement to be accurate. Moreover, backboardd not only relay touch events, also physical button click events. 

backboardd logs the exact time when a button is pressed down, and when it’s been released. 

With the help from cycript, We noticed a way that allows us to intercept that event with Objective-C Method Hooking. 

A _BKButtonEventRecord instance will be created and inserted into a global dictionary object BKEventSenderUsagePairDictionary.  We hook the insertion method when the user attempts to “turn on” the phone.

The file will unleash the SpringBoard and trigger a special code block in our injected dylib. What it does is to leverage local SSH access to gain root privilege, then we execute /bin/launchctl reboot userspace. This will exit all processes and restart the system without touching the kernel. The kernel remains patched. Hence malicious code won’t have any problem continuing to run after this kind of reboot.

The user will see the Apple Logo effect upon restarting. This is handled by backboardd as well. Upon launching the SpringBoard, the backboardd lets SpringBoard take over the screen.

From that point, the interactive UI will be presented to the user. Everything feels right as all processes have indeed been restarted. Non-persistent threats achieved “persistency” without persistence exploits.

Hijacking the Force Restart Event?

A user can perform a “force restart” by clicking rapidly on “Volume Up”, then “Volume Down”, then long press on the power button until the Apple logo appears.

We have not found an easy way to hijack the force restart event. This event is implemented at a much lower level. According to the post below, it is done at a hardware level. Following a brief search in the iOS kernel, we can confirm that we didn’t see what triggers the force-restart event. The good news is that it’s harder for malicious actors to disable force restart events, but at the same time end-users face a risk of data loss as the system does not have enough time to securely write data to disk in case of force-restart events.

Misleading Force Restart

Nevertheless, It is entirely possible for malicious actors to observe the user’s attempt to perform a  force-restart (via backboardd) and deliberately make the Apple logo appear a few seconds earlier, deceiving the user into releasing the button earlier than they were supposed to. Meaning that in this case, the end-user did not successfully trigger a force-restart.  We will leave this as an exercise for the reader.

Ref: https://support.apple.com/guide/iphone/force-restart-iphone-iph8903c3ee6/ios

NoReboot Proof of Concept

You can find the source code of NoReboot POC here.

Never trust a device to be off

Since iOS 15, Apple introduced a new feature allowing users to track their phone even when it’s been turned off. Malware researcher @naehrdine wrote a technical analysis on this feature and shared her opinion on “Security and privacy impact”. We agree with her on “Never trust a device to be off, until you removed its battery or even better put it into a Blender.”

Checking if your phone is compromised

ZecOps for Mobile leverages extended data collection and enables responding to security events. If you’d like to inspect your phone – please feel free to request a free trial here.

How iOS Malware Can Spy on Users Silently

4 November 2021 at 14:38
How iOS Malware Can Spy on Users Silently

Welcome to the first post of our latest blog series:

Mobile Attackers Mindset

In this blog series, we’re going to cover how mobile threat-actors think, and what techniques attackers use to overcome security protections and indications that our phones and tablets are compromised. 

In this first blog, we’ll demonstrate how the recently added camera & microphone green/orange indicators do not pose a real security challenge to mobile threat actors.

Starting iOS 14, there are a couple of new indicators: a green dot, and an orange dot. These indicators signal when the camera or the microphone are accessed.

We are less worried about the phone listening to us, when there’s no green/orange dot.

Source: https://support.apple.com/en-in/HT211876

We know that malware like NSO/Pegasus is capable of listening to the microphone. Can NSO Group and hundreds of other threat actors that target mobile devices can take videos of us while the visual indicator is off? 

Let’s examine if this feature poses any challenge to attackers.

Logical issues

Let’s first think about it. Is the indicator really on everytime the camera or microphone are accessed? We quickly think about Siri. How does the phone know when we say “Hey Siri” if the microphone indicator is not on all the time? The phone must be listening somehow right.

“Hey Siri” 

/System/Library/PrivateFrameworks/CoreSpeech.framework/corespeechd relies on VoiceTrigger.framework to continuously monitor the user’s voice, and then activate Siri when a keyword is heard.

Accessibility -> VoiceControl

Voice control allows you to interact with the device using voice commands.


is responsible for accessing the microphone.

Accessibility -> Switch Control

Part of the SwitchControl function is to detect the movement of the user’s head to interact with the device. Very cool feature! It’s handled by:




These features must access the microphone or camera to function. However, these features do not trigger the green/orange visual indicators. This means that mobile malware can do the same.

This means that by injecting a malicious thread into com.apple.accessibility.AccessibilityUIServer / com.apple.SpeechRecognitionCore.speechrecognitiond daemons attackers can enable silent access to the microphone. Camera access requires additional patch which we will talk about it later

Bypass TCC Prompt

TCC stands for “Transparency, Consent, and Control”. iOS users often experience this prompt:

The core of TCC is a system daemon called tccd, and it manages access to sensitive databases and the permission to collect sensitive data from input devices, including but not limited to microphone and camera.

Did you know? TCC prompt only applies to applications with UI interface. Anything running in the background requires special entitlement to operate. The entitlement looks like the picture below. Just kTCCServiceMicrophone is enough for microphone access.

Camera access is a little more complicated. In addition to tccd, there is another system daemon called mediaserverd ensures that no process with background running state can access the camera. 

So far, it looks like an extra step (e.g. patching mediaserverd) is needed to access the camera in the background while the user is interacting with another foreground application.

Disable Visual Indicators For Microphone, Camera Access

First method is rough, using Cycript to inject code into SpringBoard, causing the indicator to disappear abruptly.

Inspired by com.apple.SpeechRecognitionCore.speechrecognitiond and  com.apple.accessibility.AccessibilityUIServer, a private entitlement (com.apple.private.mediaexperience.suppressrecordingstatetosystemstatus) that fits perfectly for our purpose! Unfortunately, this method does not work for camera access.

Accessing Camera in the Background by Patching ‘mediaserverd’

mediaserverd is a daemon that monitors media capture sessions. Processes that want to access the camera must be approved by tccd as well as mediaserverd. It is an extra layer of security after tccd. It also terminates the camera access when it detects an application is no longer running in the foreground.

Noteworthy, mediaserverd is equipped with a special entitlement (get-task-allow) to prevent code injection. 

As a result of “get-task-allow” entitlement, dynamic debugger relies on obtaining task ports like cycript, frida do not work on the mediaserverd daemon. mediaserverd also gets killed by the system frequently when it’s not responding, even for a short time. It’s not common: these signs are telling us that mediaserverd is in-charge of something important.

When a process switches to the background, mediaserverd will get notified and revoke camera access for that particular process. We need to find a way to make mediaserverd do nothing when it detects that the process is running in the background.

Following a brief research we found that it is possible to prevent mediaserverd from revoking camera access by hooking into an Objective-C method -[FigCaptureClientSessionMonitor _updateClientStateCondition:newValue:], so no code overwriting is required. 

To inject into mediaserverd, we used lldb. Lldb does not rely on task-port, instead it calls the kernel for code-injection. In reality, threat-actors that already have kernel code execution capabilities can replace the “entitlements” of mediaserverd to perform such injection.

POC source code is available here.

And on Mac…

From previous experiments back in 2015, the green light next to the front camera on a Mac, cannot be turned off using software only. Modifying the AppleCameraInterface driver and uploading a custom webcam firmware did not do the trick.The green light cannot be turned off since it lights when the camera is powered on. The light remains on as long as there’s power. Hardware-based indicators are ideal from a privacy perspective. We have not validated this on recent Mac versions / HW.


We made a demonstration, accessing the camera/microphone from the background process, and streaming video/audio using the RTMP protocol, steps:

  1. Set up RTMP server
  2. Compile mediaserver_patch, inject the code into mediaserverd
  3. Compile ios_streaming_cam, re-sign the binary with following entitlements and run it in the background
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">

By: 08Tc3wBB

Use-After-Free in Voice Control: CVE-2021-30902 Write-up

27 October 2021 at 05:53
Use-After-Free in Voice Control: CVE-2021-30902 Write-up

By: 08Tc3wBB

Voice Control is a powerful feature introduced by Apple in iOS 13 and macOS Catalina. It acts as a substitute for all the touch gestures on the screen, letting you interact with the device using your voice to tap, swipe, type, and more.

com.apple.SpeechRecognitionCore.speechrecognitiond Crashes

com.apple.SpeechRecognitionCore.speechrecognitiond is a system XPCService process that handles voice control. 

During an investigation of ZecOps Mobile XDR / Mobile DFIR, we discovered a series of crashes that appears intriguing:

Exception Type:  EXC_BAD_ACCESS (SIGBUS)
Exception Subtype: EXC_ARM_DA_ALIGN at 0x0074616f6c460003

Exception Subtype: KERN_INVALID_ADDRESS at 0x2000002400000000 -> 0x0000002400000000 

Exception Subtype: KERN_INVALID_ADDRESS at 0x00000000100c02d8

Mobile Device Investigation Analysis

Not all crashes are the same, but they all have a similar pattern. All crashes occur after some libdispatch.dylib calls. 

With this clue, we went on and investigated the cause of this crash.

We’re going to explain two of the most typical cases. Both have occurred when the user is toggling the voice control switch at different timings.

We will attach a POC that demonstrates an unexpected multithread issue, proving that even when the developer uses an optimized thread management library such as Grand Central Dispatch (GCD) dispatch queues, which are already considered safe from multi-threading perspective. The chance of race condition still exists, capable of causing memory corruption and leading to code execution.

Following shows two of the most typical cases:

Race Condition Case 1: When the user turns VoiceControl off

The dispatch queue “RDAudioBufferQueue” is created when the device is actively processing audio data. An AVSoundInput class instance has been passed to this thread to provide the input data through context data. Due to insufficient consideration of thread safety, when the user decides to turn off the voice control feature, the context data may get released early in another queue “RDMainQueue“, which leads to a Use-After-Free (UAF).

There is actually a function dealing with the audio format conversion that is executed between _dispatch_call_block_and_release and <PC Corruption>. It didn’t show in the backtrace because it used the “br” instruction that doesn’t save the return address in the stack. 

Below is the pseudo-code of the function:

(1) Inside _addRecordedSpeechSampleData:length: method, It’s trying to invoke a function pointer stored in *( *(context_data + 48) + 16), normally it will execute EARCSpeechRecognitionAudioBufferAddAudioSamples. However, if the user decides to turn off voice control, the context_data will be released in another thread, as shown below:

The problem is the lack of a lock to ensure that RDAudioBufferQueue will exit before context_data is released. The heap memory in *(context_data + 48) could be released early and reoccupied by other data, which led to Program Counter (PC) corruption.

Race Condition Case 2: When the user turns VoiceControl on

RDMainQueue may randomly occur memory corruption on different objects. The above example is that the XPC connection object was released before use, and Use-After-Free causes the thread to crash.

RDMainQueue is used as a multi-purpose general queue. Various callbacks throw tasks into this queue, including accepting and handling xpc requests, reporting audio data feedback and taking action, nested calling was frequently involved.. All these tasks are submitted to RDMainQueue through dispatch_async, and they seem to be correct in the order of calling. 

How does a Use-After-Free happen if the use and release of XPC connection objects are all assigned to be processed in the same queue, in the correct order ?

The answer is that dispatch_async does not guarantee the blocks to be executed in the same order it gets called! 

Some developers may not be aware of this behavior of dispatch_async.

The following POC demonstrates the potential threat of using dispatch_async even on a same queue:

dispatch_sync is safer as if you replace all dispatch_async to dispatch_sync, the above code will run flawlessly.

dispatch_async brings the convenience of supporting nested calls. However, for the sake of thread safety, developers better to implement additional checks to ensure that the blocks are executed in the required order.

When the user frequently turns on and off the voice control switch, the busy operation of adding queues will mess up the order, which may still use an object after it’s released.

Triggering of the Crash

You can trigger this Use-After-Free while on the lock screen, following these steps:

1. Press and hold the side button to activate Siri.

2. Say “Turn Off Voice Control”, a window of voice control switch should appear.

 3. Switch on/off Voice Control repeatedly. There is some trick to trigger the crash more reliably.

Instead of using Siri, you can also just go to Settings -> Accessibility -> Voice Control

After turning on Voice Control, an icon will appear on the left upper corner, and it first appears as grey, then it will turn blue. The trick to find the best timing is to turn off the voice control right before the icon turns to blue.

The Recent iOS 0-Click, CVE-2021-30860, Sounds Familiar. An Unreleased Write-up: One Year Later

14 September 2021 at 19:11
The Recent iOS 0-Click, CVE-2021-30860, Sounds Familiar. An Unreleased Write-up: One Year Later


ZecOps identified and reproduced an Out-Of-Bounds Write vulnerability that can be triggered by opening a malformed PDF. This vulnerability reminded us of the FORCEDENTRY vulnerability exploited by NSO/Pegasus according to the CitizenLabs blog.

As a brief background: ZecOps have analyzed several devices of Al-Jazeera journalists in the summer 2020 and automatically and successfully found compromised devices without relying on any IOC. These attacks were later attributed to NSO / Pegasus.
ZecOps Mobile EDR and Mobile XDR are available here.

Noteworthy, although these two vulnerabilities are different – they are close enough and worth a deeper read.


  • We reported this vulnerability on September 1st, 2020 – iOS 14 beta was vulnerable at the time.
  • The vulnerability was patched on September 14th, 2020 – iOS 14 beta release.
  • Apple contacted us on October 20, 2020 – claiming that the bug was already fixed – (“We were unable to reproduce this issue using any current version of iOS 14. Are you able to reproduce this issue using any version of iOS 14? If so, we would appreciate any additional information you can provide us, such as an updated proof-of-concept.”).
    No CVE was assigned.

It is possible that NSO noticed this incremental bug fix, and dived deeper into CoreGraphics.

The Background

Earlier last year, we obtained a PDF file that cannot be previewed on iOS. The PDF sample crashes previewUI with segmentation fault, meaning that a memory corruption was triggered by the PDF.

Open the PDF previewUI flashes and shows nothing:

The important question is: how do we find out the source of the memory corruption?

The MacOS preview works fine, no crash. Meaning that it’s the iOS library that might have an issue. We confirmed the assumption with the iPhone Simulator, since the crash happened on the iPhone Simulator.

It’s great news since Simulator on MacOS provides better debug tools than iOS. However, having debug capability is not enough since the process crashes only when the corrupted memory is being used, which is AFTER the actual memory corruption.

We need to find a way to trigger the crash right at the point the memory corruption happens.

The idea is to leverage Guard Malloc or Valgrind, making the process crash right at the memory corruption occurs.

“Guard Malloc is a special version of the malloc library that replaces the standard library during debugging. Guard Malloc uses several techniques to try and crash your application at the specific point where a memory error occurs. For example, it places separate memory allocations on different virtual memory pages and then deletes the entire page when the memory is freed. Subsequent attempts to access the deallocated memory cause an immediate memory exception rather than a blind access into memory that might now hold other data.”

Environment Variables Injection

In this case we cannot simply add an environment variable with the command line since the previewUI launches on clicking the PDF which does not launch from the terminal, we need to inject libgmalloc before the launch.

The process “launchd_sim” launches Simulator XPC services with a trampoline process called “xpcproxy_sim”. The “xpcproxy_sim” launches target processes with a posix_spawn system call, which gives us an opportunity to inject environment variables into the target process, in this case “com.apple.quicklook.extension.previewUI”.

The following lldb command “process attach –name xpcproxy_sim –waitfor” allows us to attach xpcproxy_sim then set a breakpoint on posix_spawn once it’s launched.

Once the posix_spawn breakpoint is hit, we are able to read the original environment variables by reading the address stored in the $r9 register.

By a few simple lldb expressions, we are able to overwrite one of the environment variables into “DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib”, injection complete.

Continuing execution, the process crashed almost right away.

Analyzing the Crash

Finally we got the Malloc Guard working as expected, the previewUI crashes right at the memmove function that triggers the memory corruption.

After libgmalloc injection we have the following backtrace that shows an Out-Of-Bounds write occurs in “CGDataProviderDirectGetBytesAtPositionInternal”.

Thread 3 Crashed:: Dispatch queue: PDFKit.PDFTilePool.workQueue
0   libsystem_platform.dylib      	0x0000000106afc867 _platform_memmove$VARIANT$Nehalem + 71
1   com.apple.CoreGraphics        	0x0000000101b44a98 CGDataProviderDirectGetBytesAtPositionInternal + 179
2   com.apple.CoreGraphics        	0x0000000101d125ab provider_for_destination_get_bytes_at_position_inner + 562
3   com.apple.CoreGraphics        	0x0000000101b44b09 CGDataProviderDirectGetBytesAtPositionInternal + 292
4   com.apple.CoreGraphics        	0x0000000101c6c60c provider_with_softmask_get_bytes_at_position_inner + 611
5   com.apple.CoreGraphics        	0x0000000101b44b09 CGDataProviderDirectGetBytesAtPositionInternal + 292
6   com.apple.CoreGraphics        	0x0000000101dad19a get_chunks_direct + 242
7   com.apple.CoreGraphics        	0x0000000101c58875 img_raw_read + 1470
8   com.apple.CoreGraphics        	0x0000000101c65611 img_data_lock + 10985
9   com.apple.CoreGraphics        	0x0000000101c6102f CGSImageDataLock + 1674
10  com.apple.CoreGraphics        	0x0000000101a2479e ripc_AcquireRIPImageData + 875
11  com.apple.CoreGraphics        	0x0000000101c8399d ripc_DrawImage + 2237
12  com.apple.CoreGraphics        	0x0000000101c68d6f CGContextDrawImageWithOptions + 1112
13  com.apple.CoreGraphics        	0x0000000101ab7c94 CGPDFDrawingContextDrawImage + 752

With the same method, we can take one step further, with the MallocStackLogging flag libgmalloc provides, we can track the function call stack at the time of each allocation.

After setting the “MallocStackLoggingNoCompact=1”, we got the following backtrace showing that the allocation was inside CGDataProviderCreateWithSoftMaskAndMatte.

ALLOC 0x6000ec9f9ff0-0x6000ec9f9fff [size=16]:
0x7fff51c07b77 (libsystem_pthread.dylib) start_wqthread |
0x7fff51c08a3d (libsystem_pthread.dylib) _pthread_wqthread |
0x7fff519f40c4 (libdispatch.dylib) _dispatch_workloop_worker_thread |
0x7fff519ea044 (libdispatch.dylib) _dispatch_lane_invoke |
0x7fff519e9753 (libdispatch.dylib) _dispatch_lane_serial_drain |
0x7fff519e38cb (libdispatch.dylib) _dispatch_client_callout |
0x7fff519e2951 (libdispatch.dylib) _dispatch_call_block_and_release |
0x7fff2a9df04d (com.apple.PDFKit) __71-[PDFPageBackgroundManager forceUpdateActivePageIndex:withMaxDuration:]_block_invoke |
0x7fff2a9dfe76 (com.apple.PDFKit) -[PDFPageBackgroundManager _drawPageImage:forQuality:] |
0x7fff2aa23b85 (com.apple.PDFKit) -[PDFPage imageOfSize:forBox:withOptions:] |
0x7fff2aa23e1e (com.apple.PDFKit) -[PDFPage _newCGImageWithBox:bitmapSize:scale:offset:backgroundColor:withRotation:withAntialiasing:withAnnotations:withBookmark:withDelegate:] |
0x7fff2aa22a40 (com.apple.PDFKit) -[PDFPage _drawWithBox:inContext:withRotation:isThumbnail:withAnnotations:withBookmark:withDelegate:] |
0x7fff240bdfe0 (com.apple.CoreGraphics) CGContextDrawPDFPage |
0x7fff240bdac4 (com.apple.CoreGraphics) CGContextDrawPDFPageWithDrawingCallbacks |
0x7fff244bb0b1 (com.apple.CoreGraphics) CGPDFScannerScan | 0x7fff244bab02 (com.apple.CoreGraphics) pdf_scanner_handle_xname |
0x7fff2421e73c (com.apple.CoreGraphics) op_Do |
0x7fff2414dc94 (com.apple.CoreGraphics) CGPDFDrawingContextDrawImage |
0x7fff242fed6f (com.apple.CoreGraphics) CGContextDrawImageWithOptions |
0x7fff2431999d (com.apple.CoreGraphics) ripc_DrawImage |
0x7fff240ba79e (com.apple.CoreGraphics) ripc_AcquireRIPImageData |
0x7fff242f6fe8 (com.apple.CoreGraphics) CGSImageDataLock |
0x7fff242f758b (com.apple.CoreGraphics) img_image |
0x7fff24301fe2 (com.apple.CoreGraphics) CGDataProviderCreateWithSoftMaskAndMatte |
0x7fff51bddad8 (libsystem_malloc.dylib) calloc |
0x7fff51bdd426 (libsystem_malloc.dylib) malloc_zone_calloc 

The Vulnerability

The OOB-Write vulnerability happens in the function “CGDataProviderDirectGetBytesAtPositionInternal” of CoreGraphics library, the allocation of the target memory was inside the function “CGDataProviderCreateWithSoftMaskAndMatte“.

It allocates 16 bytes of memory if the “bits_per_pixel” equals or less than 1 byte, which is less than copy length.

We came out with a minimum PoC and reported to Apple on September 1st 2020, the issue was fixed on the iOS 14 release. We will release this POC soon.

ZecOps Mobile EDR & Mobile XDR customers are protected against NSO and are well equipped to discover other sophisticated attacks including 0-days attacks.

NSO Exploits Still Remain Mysterious: ZecOps Can Help You Fight Back

19 July 2021 at 09:57
NSO Exploits Still Remain Mysterious: ZecOps Can Help You Fight Back

This weekend, the Guardian released a groundbreaking report that authoritarian governments have breached the mobile devices of human rights activists, journalists, and lawyers across the world, using a hacking software sold by NSO Group.

While the Guardian report contained new information regarding the targets of NSO customers and the Pegasus spyware, the specific mobile vulnerabilities that NSO leveraged were not identified and remain unpatched. This means that despite the public attribution, NSO tools remain just as powerful today as they were prior to the report.

Fight Back with ZecOps!

ZecOps suspects that NSO will quickly alter its methods so that the specific IOCs, or indicators of compromise, will be difficult to identify by mobile security researchers. However, with our Mobile EDR product, we can help your organization identify NSO attacks automatically by focusing on the behavior that remains present in the logs in addition to the publically known IOCs.

Note: ZecOps does not collect or access your personal information. ZecOps will only collect information required for mobile security investigations, leaving personal information out.

Inspecting Your Phone – Free Inspection by ZecOps

To help discover these attacks, ZecOps is offering, for a limited time, free mobile inspections to businesses that were targeted by the NSO. Please fill out the form below, and a member of our team will be in touch with you.

We got your request.
Please make sure that you have filled in all the fields.

Threat Actors are Working Together. Defenders Should Collaborate Too!

17 July 2021 at 20:14
Threat Actors are Working Together. Defenders Should Collaborate Too!

We previously published that we suspected that there were more than one threat actor targeting the Al-Jazeera journalists.


ZecOps discovered NSO attacks that targeted Al-Jazeera automatically using ZecOps Mobile EDR & DFIR solutions. Our initial analysis suggested that the footprint does not belong only to NSO.

ZecOps Mobile Threat Intelligence Brief

ZecOps can now confirm, with high-confidence, that the attacks targeting journalists in Middle East were caused by at least two commercial threat actors working together, and the attack launched by a nation-state that purchased NSO’s exploit-platform. 

This may sound like a minor detail, but every detail in attribution is crucial.


In the last transparency report by NSO, they published that they limit the usage.

NSO published limit

This can be translated into “We have a license based attack-as-a-service model. We only sell a certain amount of attack licenses to a certain buyer.”. While this intelligence is not fully confirmed, and should be taken with a grain of salt, our intelligence also suggests that “Desert Cobra” (state actor) purchased an ‘unlimited number of licenses’ package to carry out attacks using NSO’s software.

NSO acquired exploits from another supplier and leveraged the borrowed exploit in the attack launched by the end-buyer (Desert Cobrat) against Al-Jazeera journalists. It is unclear if the  end-customer, a government, was aware that parts of the exploit chain were obtained from another threat-actor and sold as a package.

Bottom line

As threat actors working together, defenders should be working together too. We hope that the vendors reading this will enable SOCs around the world with better access in-order to find and capture payloads by threat actors like NSO, that will always find a way, or as we can see in this post “buy their way”, to bypass all the existing mitigations and security controls.

ZecOps Mobile EDR Customers: no further action is required. ZecOps discovered the Al-Jazeera attack, and other NSO related incidents automatically. The deployed systems detect these activities. The complete report and full IOC list is available in ZecOps Mobile Threat Intelligence feed.

Al Jazeera NSO Attack IOCs:

  • /private/var/tmp/uevkjdwxijvah/c
  • /private/var/db/com.apple.xpc.roleaccountd.staging/launchafd
  • /private/var/db/com.apple.xpc.roleaccountd.staging/rs
  • /private/var/db/com.apple.xpc.roleaccountd.staging/natgd

Unfortunately, due to iOS sandbox restrictions, it is not trivial to check for these IOCs. If you would like to check your phone for these IOCs, other attacks by NSO, or other threat actors, please feel free to contact us here.

Free Mobile Inspection

To help discover these attacks, for a limited time, ZecOps is offering free mobile inspections to businesses that were targeted in the NSO leak. For your instant inspection, fill out the form below.

We got your request.
Please make sure that you have filled in all the fields.

Meet WiFiDemon – iOS WiFi RCE 0-Day Vulnerability, and a Zero-Click Vulnerability That Was Silently Patched

17 July 2021 at 13:30
Meet WiFiDemon – iOS WiFi RCE 0-Day Vulnerability, and a Zero-Click Vulnerability That Was Silently Patched

The TL;DR Version:

ZecOps Mobile EDR Research team investigated if the recently announced WiFi format-string bug in wifid was exploited in the wild. 

This research led us to interesting discoveries:

  • Recently a silently patched 0-click WiFi proximity vulnerability on iOS 14 – iOS 14.4 without any assigned CVE
  • That the publicly announced WiFi Denial of Service (DoS) bug, which is currently a 0day, is more than just a DoS and actually a RCE!
  • Analysis if any of the two bugs were exploited across our cloud user-base.


There’s a new WiFi vulnerability in-town. You probably already saw it, but didn’t realize the implication. The recently disclosed ‘non-dangerous’ WiFi bug – is potent.

This vulnerability allows an attacker to infect a phone/tablet without *any* interaction with an attacker. This type of attack is known as “0-click” (or “zero-click”). The vulnerability was only partially patched.

1. Prerequisites to the WiFiDemon 0-Click Attack:

  • Requires the WiFi to be open with Auto-Join (enabled by default)
  • Vulnerable iOS Version for 0-click: Since iOS 14.0
  • The 0-Click vulnerability was patched on iOS 14.4


  • Update to the latest version, 14.6 at the time of writing to avoid risk of WiFiDemon in its 0-click form. 
  • Consider disabling WiFi Auto-Join Feature via Settings->WiFi->Auto-Join Hotspot->Never.
  • Perform risk and compromise assessment to your mobile/tablet security using ZecOps Mobile EDR in case you suspect that you were targeted.

2. Prerequisites to the WiFi 0Day Format Strings Attack:

Unlike initial research publications, at the time of writing, the WiFi Format Strings seem to be a Remote Code Execution (RCE) when joining a malicious SSID. 


  • Do not join unknown WiFis.
  • Consider disabling WiFi Auto-Join Feature via Settings->WiFi->Auto-Join Hotspot->Never.
  • Perform risk and compromise assessment to your mobile/tablet security using ZecOps Mobile EDR in case you suspect that you were targeted.
  • This vulnerability is still a 0day at the time of writing, July 4th. iOS 14.6 is VULNERABLE when connecting to a specially crafted SSID. 
  • Wait for an official update by Apple and apply it as soon as possible.

Wi-Fi-Demon ?

wifid is a system daemon that handles protocol associated with WIFI connection. Wifid runs as root. Most of the handling functions are defined in the CoreWiFi framework, and these services are not accessible from within the sandbox. wifid is a sensitive daemon that may lead to whole system compromise.

Lately, researcher Carl Schou (@vm_call) discovered that wifid has a format string problem when handling SSID.


The original tweet suggests that this wifid bug could permanently disable iPhone’s WiFi functionality, as well as the Hotspot feature. This “WiFi” Denial of Service (DoS) is happening since wifid writes known wifi SSID into the following three files on the disk:

  • /var/preferences/com.apple.wifi.known-networks.plist
  • /var/preferences/SystemConfiguration/com.apple.wifi-networks.plist.backup
  • /var/preferences/SystemConfiguration/com.apple.wifi-private-mac-networks.plist

Every time that wifid respawns, it reads the bad SSID from a file and crashes again. Even a reboot cannot fix this issue.

However, this bug can be “fixed” by taking the following steps according to Forbes:

“The fix is simple: Simply reset your network settings by going to Settings > General > Reset > Reset Network Settings.”

This bug currently affects the latest iOS 14.6, and Apple has not yet released any fixes for this bug.

Further Analysis Claims: This is Only a Denial of Service

Followed by another researcher Zhi @CodeColorist published a quick analysis.


His conclusion was:

“For the exploitability, it doesn’t echo and the rest of the parameters don’t seem like to be controllable. Thus I don’t think this case is exploitable.

After all, to trigger this bug, you need to connect to that WiFi, where the SSID is visible to the victim. A phishing Wi-Fi portal page might as well be more effective.”

The Plot Thickens

We checked ZecOps Mobile Threat Intelligence to see if this bug was exploited in the past. We noticed that two of our EMEA users had an event related to this bug. Noteworthy, we only have access to our cloud data, and couldn’t check other on-premises clients – so we might be missing other events.

We asked ourselves: 

  1. Why would a person aware of dangerous threats connect to a network with such an odd name “%s%s…”. – Unlikely.
  2. Why would an attacker bring a tactical team to target a VIP, only to cause DoS – It still does not make sense.

Remotely exploitable, 0-click, under the hood!

Further analysis revealed that:

  1. Attackers did not need to force the user to connect. This vulnerability could be launched as a 0click, without any user interaction. A victim only needed to have your WiFi turned on to trigger the vulnerable code.
  2. This is not a DoS, but an actual RCE vulnerability for both the recently patched 0-click format-strings vulnerability, and the malicious SSID format-strings 0-day vulnerability.

This 0-click bug was patched on iOS 14.4 and credits “an anonymous researcher” for assisting. Although this is a potent 0-click bug, a CVE was not assigned.

Technical Details: Analysis of a Zero-Click WiFi Vulnerability – WiFiDemon

Let’s do a deeper dive into the technical details behind this vulnerability:

Considering the possible impact of triggering this vulnerability as a 0-click, as well as the potential RCE implications, we investigated the wifid vulnerability in depth.

When we tested this format-strings bug on an older version, similar to our clients, we noticed that wifid has intriguing logs when it is not connected to any wifi.

These logs contain SSID, which indicates that it may be affected by the same format string bug. 

We tested it and Voilà, it is affected by the same format string bug – meaning that this is a zero-click vulnerability and can be triggered without an end-user connecting to a strange named wifi.

This log is related to a common smart device behavior: Automatically scan and join known networks.

Zero-Click – Even When The Screen is Off

The iPhone scans WiFi to join every ~3 seconds while the user is actively using the phone. Furthermore, even if the user’s phone screen has been turned off, it still scans for WiFi but at a relatively lower frequency. The waiting time for the following scan will be longer and longer, from ~10 seconds to 1+ minute

As long as the WiFi is turned on this vulnerability can be triggered. If the user is connected to an existing WiFi network, an attacker can launch another attack to disconnect/de-associate the device and then launch this 0-click attack. Disconnecting a device from a WiFi is well-documented and we’ll not cover it as part of the scope for this blog.

This 0-click vulnerability is powerful: if the malicious access point has password protection and the user never joins the wifi, nothing will be saved to the disk. After turning off the malicious access point, the user’s WIFI function will be normal. A user could hardly notice if they have been attacked.

Exploiting this Vulnerability

We further analyzed whether this vulnerability can be exploited, and how:

This post assumes that the reader is aware of the concept of format-string bugs and how to exploit them. However, this bug is slightly different from the “traditional” printf format string bugs because it uses [NSString stringWithFormat:] which was implemented by Apple, and Apple removed the support for %n for security reasons. That’s how an attacker would have been able to write to the memory in an exploitation of a traditional format string bug.

Where You AT? – %@ Is Handy!

Since we cannot use %n, we looked for another way to exploit this 0-click N-Day, as well as the 1-click 0-day wifid bug. Another possible use is %@, which is uniquely used by Objective-C.

Since the SSID length is limited to 32 bytes, we can only put up to 16 Escape characters in a single SSID. Then the Escape characters we placed will process the corresponding data on the stack.

A potential exploit opportunity is if we can find an object that has been released on the stack, in that case, we can find a spray method to control the content of that memory and then use %@ to treat it as an Objective-C object, like a typical Use-After-Free that could lead to code execution.

Step 1: Find Possible Spraying Opportunities on the Stack

First, we need to design an automatic method to detect whether it is possible to tweak the data on the stack. lldb breakpoint handling script perfectly fits that purpose. Set a breakpoint right before the format string bug and link to a lldb script that will automatically scan and observe changes in the stack.

Step 2: Find an Efficient Spraying Method

Then we need a spray method that can interfere with wifid’s memory over the air. 

An interesting strategy is called Beacon Flooding Attack. It broadcasts countless Beacon frames and results in many access points appearing on the victim’s device.

To perform a beacon flooding attack, you need a wireless Dongle that costs around $10 and a Linux VM. Install the corresponding dongle firmware and a tool called mdk3. For details, please refer to this article.

As part of the beacon frame mandatory field, SSID can store a string of up to 32 bytes. wifid assigns a string object for each detected SSID. You can observe that from the log. This is the most obvious thing we can use for spray.

Now attach a debugger to wifid and start flooding the device with a list of SSIDs that can be easily recognized. Turn on the iOS wifi feature and wait until it begins automatically scanning for available WiFi. The breakpoint will get triggered and check through the stack to find traces of spray. Below is the output of the lldb script:

The thing that caught our eye is the pointer stored at stack + offset 0x18. Since the SSID can store up to 32 bytes, the shortest format string escape character such as %x will occupy two bytes, which means that we can reach the range of 16 pointers stored on the stack with a single SSID at most. So stack + offset 0x18 could be reached by the fourth escape character. And the test results tell us that data at this offset could be controlled by the content we spray.

Step 3 – Test the Ability to Remotely Control the Code Execution Flow

So in the next test, we kept the Beacon Flooding Attack running, meanwhile we built a hotspot named “DDDD%x%x%x%@”. Notice that %@ is the fourth escape character. Unsurprisingly, wifid crashes as soon as it reads the name, and it automatically respawns and crashes again as long as the hotspot is still on.

Checking the crash, it appears that the x15 register is easily affected.

Now analyze where it crashed. As the effect of %@ format specifier, it’s trying to print Objective-C Object.

The code block highlighted in yellow is the desired code execution flow. x0 is the pointer stored at stack + offset 0x18. We try to control its content through the spray and lead the situation to the typical Use-After-Free scenario. x9 is the data x0 points to. It represents isa pointer, which is the first member of the objc object data structure. As you can see in the figure, control x9 is critical to reaching that objc_msgSend call at the bottom. With more tests, we confirmed that stack + offset 0x18 indeed can be affected by the spray.

Now things have become more familiar. Pass a controlled/fake Objc object to objc_msgSend to achieve arbitrary code execution. The next challenge is finding a way to spray memory filled with ROP/JOP payload.

Step 4 – Achieving Remote Code Execution

wifid deals with a lot of wireless features. Spraying large memory wirelessly is left as an exercise for the reader. Locally, this bug can be used to build a partial sandbox escape to help achieve jailbreaking.


Ironically, the events that triggered our interest in this vulnerability were not related to an attack and the two devices were only subject to a denial of service issue that was fixed on iOS 14.6.

However, since this vulnerability was widely published, and relatively easy to notice, we are highly confident that various threat actors have discovered the same information we did, and we would like to encourage an issuance of a patch as soon as possible.

ZecOps Mobile EDR Customers will identify attacks leveraging these vulnerabilities with the tag “WiFiDemon”.

Generating an Alert Using ZecOps Mobile EDR

We have added generic rules for detection of successful exploitation to our customers.

We also provided instructions to customers on how to create a rule to see failed spraying / ASLR bypass attempts.

To summarize:

  • A related vulnerability was exploitable as a 0-click until iOS 14.4. CVE was not assigned and the vulnerability was silently patched. The patch thanks an anonymous researcher.
  • The publicly announced WiFi vulnerability is exploitable on 14.6 when connecting a maliciously crafted SSID.
  • We highly recommend issuing a patch for this vulnerability.
  • Older devices: e.g. iPhone 5s are still on iOS 12.X which is not vulnerable to the 0-click vulnerability.

If you’d like to check your phone and monitor it – feel free to reach out to us here to discuss how we can help you increase your mobile visibility using ZecOps Mobile EDR.

We would like to thank @08tc3wbb (follow), @ihackbanme (follow) and SYMaster for assisting with this blog.

iOS 14.7 fix

The fix on iOS 14.7 is as follows, it’s pretty straightforward, adding “%s” as format-string and the SSID included string as a parameter solves the issue.

New “Always-on” Application for MacOS (April updates)

22 April 2021 at 08:27
New “Always-on” Application for MacOS (April updates)

As cyberattacks targeting mobile devices are on the rise, we continue to see massive adoption from both the private and public sectors. We are really excited to share two new features which will dramatically improve the ZecOps experience! 

New “Always-on” Application for MacOS

ZecOps is making it even easier for users to perform complex investigations of their mobile devices. Now, users can inspect their mobile devices automatically each time the phone is connected to their laptop. ZecOps for Mobile supports both iOS and Android.

Send an inspection link to a customer or colleague

ZecOps administrators now have the ability to generate a unique link to download the ZecOps Collector App from the ZecOps Dashboard. The link can then be shared with the person/group whose device you wish to inspect via email, text, or Slack.

The Collector App can be downloaded on any laptop. This feature is ideal for incident responders, managed service providers, and SOC operators.

Learn more about ZecOps Mobile EDR

Introducing ZecOps Anti-Phishing Extension

8 April 2021 at 10:10
Introducing ZecOps Anti-Phishing Extension

Phishing is a common social engineering attack that is used by scammers to steal personal information, including authentication credentials and credit card numbers. Being well known for more than 30 years, phishing is still the most common attack performed by cyber-criminals. There have been several attempts at combating phishing attacks, but no attempt has been able to successfully eliminate the problem.

One of the most common attack scenarios involves the attacker sending an email or a text message to the victim. The message, pretending to be from a trustworthy entity, links to a fake website which visually matches a legitimate site. Nowadays, most browsers include limited protection to phishing, relying on a list of known phishing domains. While such protection has value, it’s still easy to bypass.

To help combat phishing attacks, we developed a browser extension that takes a different approach. Instead of trying to determine whether a visited website is a fake website used for phishing, we augment the website with additional visual information, allowing the user to make an informed decision. The user can take into account context that the browser has no way of knowing, such as the origin of the link and the sensitivity of the information about to be entered.

The website identity

One of the most common types of phishing is tricking the user into entering credentials into a fake website. The traditional way of avoiding such phishing is to check the address bar and verify that the address matches the expected, legitimate website. Such a check requires some discipline, and is easy to miss amid a busy day.

The main goal of ZecOps Anti-Phishing Extension is to make it easy to determine the identity of a website, having a visual indication that is difficult to miss.

Take a look at the following example:

Visiting a phishing website without and with the extension
Visiting a phishing website without and with the extension

In this example, the victim navigated to a fake website pretending to be paypal.com. Without the extension (left part of the image), the only difference compared to the real website is a single character in the address bar (1 instead of l in “paypal”). With the extension, the victim gets critical information just before entering his credentials:

  • The website is visited for the first time. For a website such as PayPal, which the victim probably visited multiple times before, this is a red flag.
  • The domain name is very similar to another, well known domain name. In this case, the extension is able to recognize that “paypa1.com” is visually similar to “paypal.com”, making the phishing attempt obvious.
  • The elephant image is the visual identity of the website that the extension generated for paypa1.com, which is most likely to be different from the visual identity of paypal.com. If the victim signs into paypal.com often, he might notice that the image changed and that something is wrong. Users won’t be able to remember all images for all websites, but that’s another measure of caution that can prevent a successful attack, and is more effective for websites that are visited more often.

Misleading links

Another common phishing technique involves sending a message with a link that looks legit, but leads to a different website that is controlled by the attacker. ZecOps Anti-Phishing Extension detects such links and displays a warning message:

A warning about a misleading link
A warning about a misleading link

A word about privacy

We care about our users’ privacy, and so the extension doesn’t send any information back to us. We don’t collect the websites you visit, the messages you see, or anything at all. The only data we collect is through our phishing reporting form that you can voluntarily submit.

Installing the extension

You can get the extension in the extension store for your browser:

Source code

The source code of the extension can be found on GitHub:

Other ZecOps Projects

We created this project as a community project. If you’d like to learn about the other initiatives we have at ZecOps, we invite you to learn more about ZecOps Mobile EDR / DFIR solutions here.

ZecOps Announces The Formation of Defense Advisory Board and Appoints Former Commander of Unit 8200 Ehud Scneorson as Chairman

6 April 2021 at 13:45
ZecOps Announces The Formation of Defense Advisory Board and Appoints Former Commander of Unit 8200 Ehud Scneorson as Chairman

Ehud Schneourson to provide cyberdefense expertise and tactical guidance to burgeoning mobile security startup, with additional appointments to be announced in the coming months

SAN FRANCISCO, April 1st, 2021 — ZecOps, the world’s most powerful platform to discover and analyze mobile cyber attacks, announced the formation of its international Defense Advisory Board. Ehud Schneourson, (ret.) Brigadier General and commander of Israel’s elite Unit 8200, was appointed as Chairman.

“It takes a submarine to discover other submarines, and ZecOps is the submarine we were all waiting for in the mobile security space.” said Ehud Schneourson. “Attackers used to care only about Google and Apple, but ZecOps created an entire category of problems for attackers. I’m thrilled to partner with ZecOps in their mission to protect our most sensitive assets, our mobile devices.”

“ZecOps is a true mobile EDR that is technically non-existent in the market today”, Schneourson summarized.

ZecOps’ success in the public and private sectors has been bolstered by the discovery of several advanced attacks. These include a “0-click” vulnerability on the default iOS Mail app, attacks on journalists in the Middle East, and others. 

ZecOps estimates that there are hundreds of sophisticated organizations targeting mobile devices, many of whom sell their exploits on the black market. This claim is supported by ZecOps Mobile Threat Intelligence, which has shown a rapid increase in the number of mobile cyberattacks in the past year.

“The number of attacks that we have discovered on mobile devices is mind blowing. I can’t wait to see what else we’ll discover in the years to come,” said Zuk Avraham, co-founder and CEO of ZecOps. “Mobile devices have become our ‘single factor of authentication’, and the most desirable target for attackers. Our Defense Advisory Board understands and appreciates the creativity needed to establish proper mobile cyberdefense. I’m thrilled to bring Ehud onboard, and am excited to partner with him and the world’s defense leaders”.

About ZecOps:

ZecOps develops the world’s most powerful platform to discover and analyze mobile cyber attacks. Used by world-leading governments, enterprises, and individuals globally, ZecOps Mobile EDR provides a realistic and scalable approach to mobile threat hunting. ZecOps enables automated discovery of 0-day attacks and Advanced Persistent Threats (APTs), delivering anti cyber-espionage capabilities within minutes. Headquartered in San Francisco, ZecOps was co-founded by Zuk Avraham, a security researcher and serial entrepreneur who previously founded Zimperium.  

For more information: https://www.zecops.com 

Follow ZecOps: LinkedIn | Twitter | Facebook

ZecOps Media Contact:

[email protected]

ZecOps Selected to Fast Company’s Most Innovative Companies for 2021

9 March 2021 at 19:05
ZecOps Selected to Fast Company’s Most Innovative Companies for 2021

The mobile security startup is among the top-ranked companies in the Security category

ZecOps, the automated platform for discovering mobile cyber threats has been named to Fast Company’s prestigious annual list of the World’s Most Innovative Companies for 2021. The Fast Company list honors businesses that have demonstrated the unique ability to service customers in rapidly evolving industries, like cybersecurity, with new and novel approaches.

“This is a major milestone for ZecOps, and confirms what our customers already know – that ZecOps mobile threat discovery is the most efficient way to evaluate the integrity of smartphones,” said Zuk Avraham, Co-Founder & CEO of ZecOps. “We’re grateful to Fast Company for acknowledging our innovative approach to discovering sophisticated attacks on mobile devices.”

ZecOps has seen tremendous customer growth in 2020, a time during which the company discovered several highly publicized vulnerabilities. These include a “0-click” vulnerability on the default iOS Mail app, attacks on journalists in the Middle East, and others. ZecOps is used by world-leaders, governments, leading enterprises, and targeted individuals concerned with discovering cyberattacks on mobile devices and performing threat hunting.

“In a year of unprecedented challenges, the companies on this list exhibit fearlessness, ingenuity, and creativity in the face of crisis,” said Fast Company Deputy Editor David Lidsky, who oversaw the issue with Senior Editor Amy Farley.


ZecOps is the world’s most powerful platform to discover and analyze mobile cyber attacks. Used by governments, enterprises, and individuals worldwide, ZecOps provides a realistic and scalable approach to mobile threat hunting. ZecOps enables automated discovery of 0-day attacks and Advanced Persistent Threats (APTs), delivering anti- cyber espionage analysis within minutes. Headquartered in San Francisco, ZecOps was co-founded by Zuk Avraham, a security researcher and serial entrepreneur who previously founded Zimperium. 


Fast Company is the only media brand fully dedicated to the vital intersection of business, innovation, and design, engaging the most influential leaders, companies, and thinkers on the future of business. The editor-in-chief is Stephanie Mehta. Headquartered in New York City, Fast Company is published by Mansueto Ventures LLC, along with our sister publication Inc., and can be found online at www.fastcompany.com.

Media inquiries
[email protected]

North Korea APT Might Have Used a Mobile 0day Too?

26 January 2021 at 17:37
North Korea APT Might Have Used a Mobile 0day Too?

Following Google TAG announcement that a few profiles on twitter, were part of an APT campaign targeting security Researchers. According to Google TAG, these threat actors are North Koreans and they had multiple goals of establishing credibility by publishing a well thought of blog posts as well as interacting with researchers via Direct Messages and lure them to download and run an infected Visual Studio project.


Some of the fake profiles were: @z0x55g, @james0x40, @br0vvnn, @BrownSec3Labs

Using a Chrome 0day to infect clients?

In their post, Google TAG, mentioned that the attackers were able to pop a fully patched Windows box running Chrome. 

From Google’s post:

In addition to targeting users via social engineering, we have also observed several cases where researchers have been compromised after visiting the actors’ blog. In each of these cases, the researchers have followed a link on Twitter to a write-up hosted on blog.br0vvnn[.]io, and shortly thereafter, a malicious service was installed on the researcher’s system and an in-memory backdoor would begin beaconing to an actor-owned command and control server. At the time of these visits, the victim systems were running fully patched and up-to-date Windows 10 and Chrome browser versions.

Attacking Mobile Users?

According to ZecOps Mobile Threat Intelligence, the same threat actor might have used an Android 0day too.

If you entered this blog from your Android or iOS devices – we would like to examine your device using ZecOps Mobile DFIR tool to gather additional evidence.
Please contact us as soon as convenient at [email protected]


Hear the news first

  • Only essential content
  • New vulnerabilities & announcements
  • News from ZecOps Research Team

Your subscription request to ZecOps Blog has been successfully sent.
We won’t spam, pinky swear 🤞

NTFS Remote Code Execution (CVE-2020-17096) Analysis

5 January 2021 at 19:14
NTFS Remote Code Execution (CVE-2020-17096) Analysis

This is an analysis of the CVE-2020-17096 vulnerability published by Microsoft on December 12, 2020. The remote code execution vulnerability assessed with Exploitation: “More Likely”,  grabbed our attention among the last Patch Tuesday fixes.

Diffing ntfs.sys

Comparing the patched driver to the unpatched version with BinDiff, we saw that there’s only one changed function, NtfsOffloadRead.

Diffing ntfs sys

The function is rather big, and from a careful comparison of the two driver versions, the only changed code is located at the very beginning of the function:

BinDiff - NtfsOffloadRead
uint NtfsOffloadRead(PIRP_CONTEXT IrpContext, PIRP Irp)
  PVOID decoded = NtfsDecodeFileObjectForRead(...);
  if (!decoded) {
    if (NtfsStatusDebugFlags) {
      // ...
    // *** Change 1: First argument changed from NULL to IrpContext
    NtfsExtendedCompleteRequestInternal(NULL, Irp, 0xc000000d, 1, 0);
    // *** Change 2: The following if block was completely removed
    if (IrpContext && *(PIRP *)(IrpContext + 0x68) == Irp) {
      *(PIRP *)(IrpContext + 0x68) = NULL;
    if (NtfsStatusDebugFlags) {
      // ...
    return 0xc000000d;

  // The rest of the function...

Triggering the vulnerable code

From the name of the function, we deduced that it’s responsible for handling offload read requests, part of the Offloaded Data Transfers functionality introduced in Windows 8. An offload read can be requested remotely via SMB by issuing the FSCTL_OFFLOAD_READ control code.

Indeed, by issuing the FSCTL_OFFLOAD_READ control code we’ve seen that the NtfsOffloadRead function is being called, but the first if branch is skipped. After some experimentation, we saw that one way to trigger the branch is by opening a folder, not a file, before issuing the offload read.

Exploring exploitation options

We looked at each of the two changes and tried to come up with the simplest way to cause some trouble to a vulnerable computer.

  • First change: The NtfsExtendedCompleteRequestInternal function wasn’t receiving the IrpContext parameter.

    Briefly looking at NtfsExtendedCompleteRequestInternal, it seems that if the first parameter is NULL, it’s being ignored. Otherwise, the numerous fields of the IrpContext structure are being freed using functions such as ExFreePoolWithTag. The code is rather long and we didn’t analyze it thoroughly, but from a quick glance we didn’t find a way to misuse the fact that those functions aren’t being called in the vulnerable version. We observed, thought, that the bug causes a memory leak in the non-paged pool which is guaranteed to reside in physical memory.

    We implemented a small tool that issues offload reads in an infinite loop. After a couple of hours, our vulnerable VM ran out of memory and froze, no longer responding to any input. Below you can see the Task Manager screenshots and the code that we used.

  • Second change: An IRP pointer field, part of IrpContex, was set to NULL.

    From our quick attempt, we didn’t find a way to misuse the fact that the IRP pointer field is set to NULL. If you have any ideas, let us know.

What about remote code execution?

We’re curious about that as much as you are. Unfortunately, there’s a limited amount of time that we can invest in satisfying our curiosity. We went as far as finding the vulnerable code and triggering it to cause a memory leak and an eventual denial of service, but we weren’t able to exploit it for remote code execution.

It is possible that there’s no actual remote code execution here, and it was marked as such just in case, as it happened with the “Bad Neighbor” ICMPv6 Vulnerability (CVE-2020-16898). If you have any insights, we’ll be happy to hear about them.

CVE-2020-17096 POC (Denial of Service)

Before. An idle VM with a standard configuration and no running programs.

After. The same idle VM after triggering the memory leak, unresponsive.

using (var trans = new Smb2ClientTransport())
    var ipAddress = System.Net.IPAddress.Parse(ip);
    trans.ConnectShare(server, ipAddress, domain, user, pass, share, SecurityPackageType.Negotiate, true);

        FsDirectoryDesiredAccess.GENERIC_READ | FsDirectoryDesiredAccess.GENERIC_WRITE,

    offloadReadInput.Size = 32;
    offloadReadInput.FileOffset = 0;
    offloadReadInput.CopyLength = 0;

    byte[] requestInputOffloadRead = TypeMarshal.ToBytes(offloadReadInput);

    while (true)
        trans.SendIoctlPayload(CtlCode_Values.FSCTL_OFFLOAD_READ, requestInputOffloadRead);
        trans.ExpectIoctlPayload(out _, out _);

C# code that causes the memory leak and the eventual denial of service. Was used with the Windows Protocol Test Suites.

Remote iOS Attacks Targeting Journalists: More Than One Threat Actor?

21 December 2020 at 09:30
Remote iOS Attacks Targeting Journalists: More Than One Threat Actor?

ZecOps is proud to share that we detected multiple exploits by the threat actors that recently targeted Aljazeera’s journalists before it was made public. The attack detection was automatically detected using ZecOps Mobile DFIR.

In this blog post, we’ll share our analysis of the post-exploitation kernel panics observed on one of the targeted devices.

Key details on the attacks targeting journalists in Middle East:

  • First known attack: earliest signs of compromise on January 17th, 2020.
  • Was the attack successful: Yes – the device shows signs for successfully planted malware / rootkit.
  • Persistence: The device shows signs for a persistent malware that is capable of surviving reboots. It is unclear if the device was re-infected following an OS update, or that the malware also persisted between OS updates.
  • Attack Impact: The threat-operators were able to continuously access the device microphone, camera, and data including texts, and emails for the entire period.
  • Attribution: We named this threat actor Desert Cobra. We do not rule out that NSO (aka “NSO Group”) was involved in the other reporters’ cases that was published today by Citizen Labs. We refrain from naming the particular threat actor that targeted one of the victims in Citizen-Labs report, NSO, due to some activities that do not add-up with our Mobile Threat Intelligence on NSO. We also do not rule out that this device was potentially compromised by more than one threat actor simultaneously.
  • OS Update? We do recommend updating to the latest iOS version, however we have no evidence that this actually fixes any of the vulnerabilities that were exploited by this threat operator(s).

Post-exploitation Panic Analysis

A tale of two panics: MobileMail and mediaanalysisd: kauth_cred_t corruption

The following stack backtrace of the MobileMail panic indicates that the panic happened on function kauth_cred_unref:

panic(cpu 2 caller 0xfffffff02a2f47f0): "kfree: size 8589934796 > kalloc_largest_allocated 21938176"
_func_fffffff007b747f0 + 0 ~ (kfree + 340)
sfree() + 28
_func_fffffff008debd5c + 68 ~ (_mpo_cred_check_label_update + 2904)
_func_fffffff008df5f48 + 92 ~ (_sandbox_hook_policy_syscall + 6488)
_func_fffffff008df5d8c + 300 ~ (_sandbox_hook_policy_syscall + 6252)
_func_fffffff008de309c + 64 ~ (_check_boolean_entitlement + 1716)
_func_fffffff0081538e8 + 76 ~ (audit_session_unref)
_func_fffffff007f3f790 + 200 ~ (kauth_cred_unref)
 _vn_open_auth + 1612
 _open1 + 256
 _open + 528

kauth_cred_unref frees credential structures from the kernel. The following is the stack backtrace of the mediaanalysisd panic, it also panicked on function “kauth_cred_unref”:

_func_fffffff008db5d4c + 260 ~ (_sandbox_hook_policy_syscall + 6212)         
       0xfffffff010795e50  ldr x8, [x21]                  
       0xfffffff010795e54  str x8, [x19, x22, lsl #3]     
       0xfffffff010795e58  b 0x01db5e78    // 0xfffffff01254bcd0 
       0xfffffff010795e5c  ldr x9, [x8] 
 _func_fffffff008da305c + 64 ~ (_check_boolean_entitlement + 1716)
 _func_fffffff00814783c + 76 ~ (audit_session_unref)
 _func_fffffff007f336f4 + 200 ~ (kauth_cred_unref)
_func_fffffff007cebec8 + 444 ~ (_copyin + 4560)
_copyin + 2224

Function “kauth_cred_free” calls by “kauth_cred_unref”, code as follows:

static void kauth_cred_free(kauth_cred_t cred)
assert(os_atomic_load(&cred->cr_ref, relaxed) == 0);
AUDIT_SESSION_UNREF(cred); // ← call kfree, panic inside
FREE_ZONE(cred, sizeof(*cred), M_CRED);

Both of the panics happened inside “AUDIT_SESSION_UNREF”, which means the credential structure of the processes was corrupted.

A classic way to gain root access for a kernel exploit is to replace the credential structure of an attacker controlled process with the kernel credentials. Please note that it doesn’t necessarily mean MobileMail or mediaanalysisd was controlled, the corruption of the credential structures could have also happened due to wrong offsets during exploitation.

ZecOps customers: no further action is required. The deployed systems detect these activities. The complete report and full IOC list is available in ZecOps Threat Intelligence feed.

Hear the news first

  • Only essential content
  • New vulnerabilities & announcements
  • News from ZecOps Research Team

Your subscription request to ZecOps Blog has been successfully sent.
We won’t spam, pinky swear 🤞

Crash Analysis Series: An exploitable bug on Microsoft Teams ?! A Tale of One Bit

24 November 2020 at 05:52
Crash Analysis Series: An exploitable bug on Microsoft Teams ?! A Tale of One Bit

This is a story about a Microsoft Teams crash that we investigated recently. At first glance, it looked like a possible arbitrary code execution vulnerability, but after diving deeper we realized that there’s another explanation for the crash.


  • ZecOps ingested and analyzed an event that seems exploitable on a Windows machine from Microsoft Teams
  • This machine has a lot of other anomalies
  • ZecOps verifies anomalies such as: blue screens, sudden crashes, mobile restarts without clicking on the power button; and determines if they are related to cyber attacks, software/hardware issues, or configuration problems. 
  • Spoiler alert (text beneath the black highlight):

After further analyzing the crash, we realized that the faulty hardware was causing this exploitable event to appear, and not related to an intentional attack. We suspect that a bit flip was caused due to a bad hardware component.

  • Business impact: Hardware problems are more common than we think. Repeating faulty hardware-issues lead to continuous loss of productivity, context-switches, and IT/Cyber disruptions. Identifying faulty hardware can save a lot of time. We recommend using the freely available and agent-less tool ZOTOMATE to identify what is SW/HW problems. ZecOps is leveraging machine-learning and its mobile threat intelligence, mobile DFIR, as well as endpoints and servers crash analysis solution, and mobile apps crash-analysis to perform such analysis at scale. 

The crash

Looking at the call stack, we saw that the process crashed due to a stack overflow:

 # Child-SP          RetAddr               Call Site
00 000000e5`84600f20 00007ffd`9048ebbc     ntdll!RtlDispatchException+0x3c
01 000000e5`84601150 00007ffd`9049a49a     ntdll!RtlRaiseStatus+0x5c
02 000000e5`846016f0 00007ffd`9048ebbc     ntdll!RtlDispatchException+0xa5cba
03 000000e5`84601e20 00007ffd`9049a49a     ntdll!RtlRaiseStatus+0x5c
04 000000e5`846023c0 00007ffd`9048ebbc     ntdll!RtlDispatchException+0xa5cba
05 000000e5`84602af0 00007ffd`9049a49a     ntdll!RtlRaiseStatus+0x5c
06 000000e5`84603090 00007ffd`9049350e     ntdll!RtlDispatchException+0xa5cba
07 000000e5`846037c0 00007ffd`9048eb73     ntdll!KiUserExceptionDispatch+0x2e
08 000000e5`84603f60 00007ffd`9049a49a     ntdll!RtlRaiseStatus+0x13
09 000000e5`84604500 00007ffd`9048ebbc     ntdll!RtlDispatchException+0xa5cba
0a 000000e5`84604c30 00007ffd`9049a49a     ntdll!RtlRaiseStatus+0x5c
0b 000000e5`846051d0 00007ffd`9048ebbc     ntdll!RtlDispatchException+0xa5cba
[307 more pairs of RtlRaiseStatus and RtlDispatchException]
272 000000e5`846fb670 00007ffd`9049a49a     ntdll!RtlRaiseStatus+0x5c
273 000000e5`846fbc10 00007ffd`9049350e     ntdll!RtlDispatchException+0xa5cba
274 000000e5`846fc340 00007ff7`8b93338a     ntdll!KiUserExceptionDispatch+0x2e
275 000000e5`846fcad0 00007ff7`8b922e4d     Teams!v8_inspector::V8StackTraceId::ToString+0x39152a
[More Teams frames...]
2c1 000000e5`846ffa40 00007ffd`9045a271     kernel32!BaseThreadInitThunk+0x14
2c2 000000e5`846ffa70 00000000`00000000     ntdll!RtlUserThreadStart+0x21

It can be seen from the call stack that the original exception occurred earlier, at address 00007ff7`8b93338a. Due to an incorrect exception handling, the RtlDispatchException function raised the STATUS_INVALID_DISPOSITION exception again and again in a loop, until no space was left in the stack and the process crashed. That’s an actual bug in Teams that Microsoft might want to fix, but it manifests itself only when the process is about to crash anyway, so that might not be a top priority.

The original exception

To extract the original exception that occurred on address 00007ff7`8b93338a, we did what Raymond Chen suggested in his blog post, Sucking the exception pointers out of a stack trace. Using the .cxr command with the context record structure passed to the KiUserExceptionDispatcher function, we got the following output:

0:000> .cxr 000000e5846fc340
rax=00005f5c70818010 rbx=00005f5c70808010 rcx=000074e525bf0e08
rdx=0000006225f01970 rsi=000016b544b495b0 rdi=0001000000000000
rip=00007ff78b93338a rsp=000000e5846fcad0 rbp=0000000000000009
 r8=000000e5846fcb68  r9=00000000ff000000 r10=0000000000ff0000
r11=000000cea999e331 r12=00005f5c70808010 r13=0000000000001776
r14=0000006225f01970 r15=000000e5846fcaf8
iopl=0         nv up ei pl nz na pe nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010202
00007ff7`8b93338a 488b07          mov     rax,qword ptr [rdi] ds:00010000`00000000=????????????????

The original exception was triggered by accessing an invalid pointer of the value 00010000`00000000. Not only does the pointer look invalid, It’s actually a non-canonical address in today’s hardware implementations of x86-64, which means that it can’t ever be allocated or become valid. Next, we looked at the assembly commands below the crash:

0:000> u
00007ff7`8b93338a 488b07          mov     rax,qword ptr [rdi]
00007ff7`8b93338d 4889f9          mov     rcx,rdi
00007ff7`8b933390 ff5008          call    qword ptr [rax+8]

Very interesting! If we can control the rdi register at this point of the execution, that’s a great start for arbitrary code execution. All we need to control the instruction pointer is to be able to build a fake virtual table, or to use an existing one, and the lack of support for Control Flow Guard (CFG) makes things even easier. As a side note, there’s an issue about adding CFG support which is being actively worked on.

At this point, we wanted to find answers to the following questions:

  • How can this bug be reproduced?
  • What source of input can trigger the bug? Specifically, can it be triggered remotely?
  • To what extent can the pointer be controlled?

The original exception stack trace

In order to try and reproduce the crash, we needed to gather more information about what was going on when the exception occurred. We checked the original exception stack trace and got the following:

0:000> k
 # Child-SP          RetAddr               Call Site
00 000000e5`846fcad0 00007ff7`8b922e4d     Teams!v8_inspector::V8StackTraceId::ToString+0x39152a
01 000000e5`846fcb40 00007ff7`8b92f29b     Teams!v8_inspector::V8StackTraceId::ToString+0x380fed
02 000000e5`846fcbc0 00007ff7`8b92f21f     Teams!v8_inspector::V8StackTraceId::ToString+0x38d43b
03 000000e5`846fcc00 00007ff7`8b9308c0     Teams!v8_inspector::V8StackTraceId::ToString+0x38d3bf
04 000000e5`846fcc80 00007ff7`8b064123     Teams!v8_inspector::V8StackTraceId::ToString+0x38ea60
05 000000e5`846fce10 00007ff7`8b08b411     Teams!v8::Unlocker::~Unlocker+0xf453
06 000000e5`846fce60 00007ff7`8b088f16     Teams!v8::Unlocker::~Unlocker+0x36741
07 000000e5`846fd030 00007ff7`8b087eff     Teams!v8::Unlocker::~Unlocker+0x34246
08 000000e5`846fd190 00007ff7`8b053b79     Teams!v8::Unlocker::~Unlocker+0x3322f
09 000000e5`846fd1c0 00007ff7`8b364e51     Teams!v8::Unwinder::PCIsInV8+0x22059
0a 000000e5`846fd2e0 00007ff7`8b871abd     Teams!v8::internal::TickSample::print+0x54071
0b 000000e5`846fd3d0 00007ff7`8b84e3b8     Teams!v8_inspector::V8StackTraceId::ToString+0x2cfc5d
0c 000000e5`846fd420 00005ebc`b2fdc6a9     Teams!v8_inspector::V8StackTraceId::ToString+0x2ac558
0d 000000e5`846fd468 00007ff7`8b800cb8     0x00005ebc`b2fdc6a9
[More Teams frames...]
4c 000000e5`846ffa40 00007ffd`9045a271     kernel32!BaseThreadInitThunk+0x14
4d 000000e5`846ffa70 00000000`00000000     ntdll!RtlUserThreadStart+0x21

It can be deduced from the large offsets that something is wrong with the symbols, as Raymond Chen also explains in his blog post, Signs that the symbols in your stack trace are wrong. In fact, Teams comes with no symbols, and there’s no public symbol server for it, so the symbols we see in the stack trace are some of the few functions exported by name. Fortunately, Teams is based on Electron which is open source, so we were able to match the Teams functions on the stack to the same functions in Electron. At first, we tried to do that with a binary diffing tool, but it didn’t work so well due to the executable/symbol files being so large (exe – 120 MB, pdb – 2 GB), so we ended up matching the functions manually.

Here’s what we got after matching the symbols:

 # Call Site
00 WTF::WeakProcessingHashTableHelper<...>::Process
01 blink::ThreadHeap::WeakProcessing
02 blink::ThreadState::MarkPhaseEpilogue
03 blink::ThreadState::AtomicPauseMarkEpilogue
04 blink::UnifiedHeapController::TraceEpilogue
05 v8::internal::GlobalHandles::InvokeFirstPassWeakCallbacks
06 v8::internal::Heap::CollectGarbage
07 v8::internal::Heap::CollectGarbage
08 v8::internal::Heap::HandleGCRequest
09 v8::internal::StackGuard::HandleInterrupts
0a v8::internal::Runtime_StackGuard
0b v8::internal::compiler::JSCallReducer::ReduceArrayIteratorPrototypeNext
0c Builtins_ObjectPrototypeHasOwnProperty

WTF was indeed our reaction when we saw where the exception occurred (which, of course, means Web Template Framework).

From what we can see, the hasOwnProperty object method was called, at which point the garbage collection was triggered, and the invalid pointer was accessed while processing one of its internal hash tables. Could it be that we found a memory bug in the V8 garbage collection? We believed it to be quite unlikely. And if so, how do we reproduce it?

Switching context

At this point we put the Teams crash on hold and went on to look at the other crashes which occurred on the same computer. Once we did that, it all became clear: it had several BSODs, all of the type MEMORY_CORRUPTION_ONE_BIT, indicating a faulty memory/storage hardware. And looks like that’s exactly what happened in the Teams crash: the faulty address was originally a NULL pointer, but because of a corrupted bit it became 00010000`00000000, causing the exception and the crash.


The conclusion is that the relevant computer needs to have its faulty hardware replaced, and of course there’s nothing wrong with V8’s garbage collection that has anything to do with the crash. That’s yet another reminder that hardware problems can cause various anomalies that are hard to explain, such as this Teams crash or crashing at the xor eax, eax instruction.

Hear the news first

  • Only essential content
  • New vulnerabilities & announcements
  • News from ZecOps Research Team

Your subscription request to ZecOps Blog has been successfully sent.
We won’t spam, pinky swear 🤞

Running code in the context of iOS Kernel: Part I + LPE POC on iOS 13.7

19 November 2020 at 18:57
Running code in the context of iOS Kernel: Part I + LPE POC on iOS 13.7

Abstract.  Due to its popularity, iOS has attracted the attention of a large number of security researchers.  Apple is constantly improving iOS security, develops and adapts new mitigations at a rapid pace. In terms of the effectiveness of mitigation measures, Apple increases the complexity of hacking iOS devices making it one of the hardest platforms to hack, however, it is not yet sufficient to block skilled individuals and well-funded groups from achieving remote code execution with elevated permissions, and persistence on the device.

This blog post is the first of multiple in a series of achieving elevated privileges on iOS. 

This series of posts will go all the way until privileged access is obtained, the userspace exploit, as well as persistence on the device following a reboot. The full reports are currently available to iOS Threat Intelligence subscribers of ZecOps Mobile Threat Intelligence.

We will cover in detail how chaining a few bugs leads us to run code in the context of iOS kernel. Chaining such bugs with other exploits (e.g. the iOS MailDemon vulnerability, or other webkit based bugs) allow to gain full remote control over iOS devices.

This exploit was obtained as part of ZecOps Reverse Bounty, and donated to FreeTheSandbox initiative.

Freethesandbox.org – Free The Sandbox restrictions from iOS & Android devices

We would like to thank @08Tc3wBB for participating in ZecOps Reverse Bounty, and everyone else that helped in this project. We would also like to thank the Apple Security team for fixing these bugs and preventing further abuse of these bugs in up to date versions of iOS.

As we’re planning to release the additional blogs, we are already releasing a full Local Privilege Escalation chain that works on iOS 13.7 and earlier versions on both PAC and non-PAC devices.

We are making this release fully open-source for transparency. We believe that it is the best outcome to improve iOS research and platform security.

You may access the source here: https://github.com/ZecOps/FreeTheSandbox_LPE_POC_13.7

The Vulnerabilities – Part I

AppleAVE2 is a graphics IOKit driver that runs in kernel space and exists only on iOS and just like many other iOS-exclusive drivers, it’s not open-source and most of the symbols have been removed. 

The driver cant be accessed from the default app sandbox environment, which reduces the chances of thorough analysis by Apple engineers or other researchers. The old implementation of this driver seems like a good attack surface and the following events demonstrate this well. 

iOS Threat Intelligence

Back in 2017,  7 vulnerabilities were exposed in the same driver, by Adam Donenfeld of the Zimperium zLabs Team,

From the description of these vulnerabilities, some remain attractive even today, while powerful mitigations like PAC (for iPhones/iPads with A12 and above) and zone_require (iOS 13 and above) are present, arbitrary memory manipulation vulnerabilities such as CVE-2017-6997, CVE-2017-6999 play a far greater role than execution hijacking type, have great potential when used in chain with various information leakage vulnerabilities.

Despite the fact that these vulnerabilities have CVEs, which generally indicating that they have been fixed, Apple previously failed to fix bugs in one go and even bug regressions. With that in-mind, let’s commence our journey to hunt the next AVE vulnerability! 

We will start off from the user-kernel data interaction interface:

user-kernel data

AppleAVE2 exposes 9 (index 0-8) methods via rewriting IOUserClient::externalMethod:

Two exposed methods (index 0 and 1) allow to add or remove clientbuf(s), by the FIFO order.

The rest of the methods (index 3-8) are all eventually calling AppleAVE2Driver::SetSessionSettings through IOCommandGate to ensure thread-safe and avoid racing.

 *1 Overlapping Segment Attack against dyld to achieve untethered jailbreak, first appearance in iOS 6 jailbreak tool — evasi0n, then similar approach shown on every public jailbreak, until after Pangu9, Apple seems finally eradicated the issue.
*2  Apple accidentally re-introduces previously fixed security flaw in a newer version.

We mainly use method at index 7 to encode a clientbuf, which basically means to load many IOSurfaces via IDs provided from userland, and use method at index 6 to trigger trigger the multiple security flaws located inside AppleAVE2Driver::SetSessionSettings.

The following chart entails a relationship map between salient objects:

clientbuf is memory buffer allocated via IOMalloc, with quite significant size (0x29B98 in iOS 13.2).

Every clientbuf objext thats is being added contains pointers to the front and back, forming a double-linked list, so that the AppleAVE2Driver’s instance stores only the first clientbuf pointer.

The clientbuf contains multiple MEMORY_INFO structures. When user-space provides IOSurface, an iosurfaceinfo_buf will be allocated and then used to fill these structures.

iosurfaceinfo_buf contains a pointer to AppleAVE, as well as variables related to mapping from user-space to kernel-space.

As part of the clientbuf structure, the content of these InitInfo_block(s) is copied from user-controlled memory through IOSurface, this happens when the user first time calls another exposed method(At index 7) after adding a new clientbuf.

m_DPB is related to arbitrary memory reading primitive which will be explained later in this post.

Brief Introduction to IOSurface

In case if you are not familiar with IOSurface, read the below:

According to Apple’s description IOSurface is used for sharing hardware-accelerated buffer data ( for framebuffers and textures) more efficiently across multiple processes.

Unlike AppleAVE, an IOSurface object can be easily created by any userland process (using IOSurfaceRootUserClient). When creating an IOSurface object you will get a 32 bit long Surface ID number for indexing purposes in the kernel so that the kernel will be able to map the userspace memory associated with the object into kernel space.  

Now with these concepts in mind let’s talk about the AppleAVE vulnerabilities. 

The First Vulnerability (iOS 12.0 – iOS 13.1.3)

The first AppleAVE vulnerability has given CVE-2019-8795 and together with other two vulnerabilities — A Kernel Info-Leak(CVE-2019-8794) that simply defeats KASLR, and a Sandbox-Escape(CVE-2019-8797) that’s necessary to access AppleAVE, created an exploit chain on iOS 12 that was able to jailbreak the device. That’s until the final release of iOS 13, which  destroyed the Sandbox-Escape by applying sandbox rules to the vulnerable process and preventing it from accessing AppleAVE, So the sandbox escape was replaced with another sandbox escape vulnerability that was discussed before. 

The first AppleAVE vulnerability was eventually fixed after the update of iOS 13.2.

Here is a quick description about it and for more detailed-write up you can look at a previous writeup.

When a user releases a clientbuf, it will go through every MEMORY_INFO that the clientbuf contains and will attempt to unmap and release related memory resources.

The security flaw is quite obvious if you compare to how Apple fixed it:

The unfixed version has defect code due to an  out-of-bounds access that allows an attacker to hijack kernel code execution in regular and PAC-enabled devices. This flaw can also become an arbitrary memory release primitive via the operator delete. and back then, before Apple fixed zone_require flaw on iOS 13.6, that was enough to achieve jailbreak on the latest iOS device.

The POC released today is just an initial version that will allow others to take it further. The POC shares basic analytics data with ZecOps to find additional vulnerabilities and help further secure iOS – this option can be disabled in the source.

In the next posts we’ll cover:

  • Additional vulnerabilities in the kernel
  • Exploiting these vulnerabilities
  • User-space vulnerabilities
  • The ultimate persistence mechanism that is likely to never be patched

Hear the news first

  • Only essential content
  • New vulnerabilities & announcements
  • News from ZecOps Research Team

Your subscription request to ZecOps Blog has been successfully sent.
We won’t spam, pinky swear 🤞

Exploring the Exploitability of “Bad Neighbor”: The Recent ICMPv6 Vulnerability (CVE-2020-16898)

11 November 2020 at 19:59
Exploring the Exploitability of “Bad Neighbor”: The Recent ICMPv6 Vulnerability (CVE-2020-16898)

At the Patch Tuesday on October 13, Microsoft published a patch and an advisory for CVE-2020-16898, dubbed “Bad Neighbor”, which was undoubtedly the highlight of the monthly series of patches. The bug has received a lot of attention since it was published as an RCE vulnerability, meaning that with a successful exploitation it could be made wormable. Initially, it was graded with a high CVSS score of 9.8/10, though it was later lowered to 8.8.

In days following the publication, several write-ups and POCs were published. We looked at some of them:

The writeup by pi3 contains details that are not mentioned in the writeup by Quarkslab. It’s important to note that the bug can only be exploited when the source address is a link-local address. That’s a significant limitation, meaning that the bug cannot be exploited over the internet. In any case, both writeups explain the bug in general and then dive into triggering a buffer overflow, causing a system crash, without exploring other options.

We wanted to find out whether something else could be done with this vulnerability, aside from triggering the buffer overflow and causing a blue screen (BSOD)

In this writeup, we’ll share our findings.

The bug in a nutshell

The bug happens in the tcpip!Ipv6pHandleRouterAdvertisement function, which is responsible for handling incoming ICMPv6 packets of the type Router Advertisement (part of the Neighbor Discovery Protocol).

The packet structure is (RFC 4861):

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|     Type      |     Code      |          Checksum             |
| Cur Hop Limit |M|O|  Reserved |       Router Lifetime         |
|                         Reachable Time                        |
|                          Retrans Timer                        |
|   Options ...

As can be seen from the packet structure, the packet consists of a 16-bytes header, followed by a variable amount of option structures. Each option structure begins with a type field and a length field, followed by specific fields for the relevant option type.

The bug happens due to an incorrect handling of the Recursive DNS Server Option (type 25, RFC 5006):

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|     Type      |     Length    |           Reserved            |
|                           Lifetime                            |
|                                                               |
:            Addresses of IPv6 Recursive DNS Servers            :
|                                                               |

The Length field defines the length of the option in units of 8 bytes. The option header size is 8 bytes, and each IPv6 address adds additional 16 bytes to the length. That means that if the structure contains n IPv6 addresses, the length is supposed to be set to 1+2*n. The bug happens when the length is an even number, causing the code to incorrectly interpret the beginning of the next option structure.

Visualizing the POC of 0xeb-bp

As a starting point, let’s visualize 0xeb-bp’s POC and get some intuition about what’s going on and why it causes a stack overflow. Here is the ICMPv6 packet as constructed in the source code:

As you can see, the ICMPv6 packet is followed by two Recursive DNS Server options (type 25), and then a 256-bytes buffer. The two options have an even length of 4, which triggers the bug.

The tcpip!Ipv6pHandleRouterAdvertisement function that parses the packet does two iterations over the option structures. The first iteration does simple checks such as verifying the length field of the structures. The second iteration actually parses the option structures. Because of the bug, each iteration interprets the packet differently.

Here’s how the first iteration sees the packet:

Each option structure is just skipped according to the length field after doing some basic checks.

Here’s how the second iteration sees it:

This time, in the case of a Recursive DNS Server option, the length field is used to determine the amount of IPv6 addresses, which is calculated as following:

amount_of_addr = (length – 1) / 2

Then, the IPv6 addresses are processed, and the next iteration continues after the last processed IPv6 address, which, in case of an even length value, happens to be in the middle of the option structure compared to what the first iteration sees. This results in processing an option structure which wasn’t validated in the first iteration. 

Specifically in this POC, 34 is not a valid length for option of the type 24, but because it wasn’t validated, the processing continues and too many bytes are copied on the stack, causing a stack overflow. Noteworthy, fragmentation is required for triggering the stack overflow (see the Quarkslab writeup for details).

Zooming out

Now we know how to trigger a stack overflow using CVE-2020-16898, but what are the checks that are made in each of the mentioned iterations? What other checks, aside from the length check, can we bypass using this bug? Which option types are supported, and is the handling different for each of them? 

We didn’t find answers to these questions in any writeup, so we checked it ourselves.

Here are the relevant parts of the Ipv6pHandleRouterAdvertisement function, slightly simplified:

void Ipv6pHandleRouterAdvertisement(...)
    // Initialization and other code...

    if (!IsLinkLocalAddress(SrcAddress) && !IsLoopbackAddress(SrcAddress))
        // error

    // Initialization and other code...

    NET_BUFFER NetBuffer = /* ... */;

    // First loop
    while (NetBuffer->DataLength >= 2)
        BYTE TempTypeLen[2];
        BYTE* TempTypeLenPtr = NdisGetDataBuffer(NetBuffer, 2, TempTypeLen, 1, 0);
        WORD OptionLenInBytes = TempTypeLenPtr[1] * 8;
        if (OptionLenInBytes == 0 || OptionLenInBytes > NetBuffer->DataLength)
            // error

        BYTE OptionType = TempTypeLenPtr[0];
        switch (OptionType)
        case 1: // Source Link-layer Address
            // ...

        case 3: // Prefix Information
            if (OptionLenInBytes != 0x20)
                // error

            BYTE TempPrefixInfo[0x20];
            BYTE* TempPrefixInfoPtr = NdisGetDataBuffer(NetBuffer, 0x20, TempPrefixInfo, 1, 0);
            BYTE PrefixInfoPrefixLength = TempRouteInfoPtr[2];
            if (PrefixInfoPrefixLength > 128)
                // error

        case 5: // MTU
            // ...

        case 24: // Route Information Option
            if (OptionLenInBytes > 0x18)
                // error

            BYTE TempRouteInfo[0x18];
            BYTE* TempRouteInfoPtr = NdisGetDataBuffer(NetBuffer, 0x18, TempRouteInfo, 1, 0);
            BYTE RouteInfoPrefixLength = TempRouteInfoPtr[2];
            if (RouteInfoPrefixLength > 128 ||
                (RouteInfoPrefixLength > 64 && OptionLenInBytes < 0x18) ||
                (RouteInfoPrefixLength > 0 && OptionLenInBytes < 0x10))
                // error

        case 25: // Recursive DNS Server Option
            if (OptionLenInBytes < 0x18)
                // error

            // Added after the patch - this it the fix
            //if (OptionLenInBytes - 8 % 16 != 0)
            //    // error

        case 31: // DNS Search List Option
            if (OptionLenInBytes < 0x10)
                // error

        NetBuffer->DataOffset += OptionLenInBytes;
        NetBuffer->DataLength -= OptionLenInBytes;
        // Other adjustments for NetBuffer...

    // Rewind NetBuffer and do other stuff...

    // Second loop...
    while (NetBuffer->DataLength >= 2)
        BYTE TempTypeLen[2];
        BYTE* TempTypeLenPtr = NdisGetDataBuffer(NetBuffer, 2, TempTypeLen, 1, 0);
        WORD OptionLenInBytes = TempTypeLenPtr[1] * 8;
        if (OptionLenInBytes == 0 || OptionLenInBytes > NetBuffer->DataLength)
            // error

        BOOL AdvanceBuffer = TRUE;

        BYTE OptionType = TempTypeLenPtr[0];
        switch (OptionType)
        case 3: // Prefix Information
            BYTE TempPrefixInfo[0x20];
            BYTE* TempPrefixInfoPtr = NdisGetDataBuffer(NetBuffer, 0x20, TempPrefixInfo, 1, 0);
            BYTE PrefixInfoPrefixLength = TempRouteInfoPtr[2];
            // Lots of code. Assumptions:
            // PrefixInfoPrefixLength <= 128

        case 24: // Route Information Option
            BYTE TempRouteInfo[0x18];
            BYTE* TempRouteInfoPtr = NdisGetDataBuffer(NetBuffer, 0x18, TempRouteInfo, 1, 0);
            BYTE RouteInfoPrefixLength = TempRouteInfoPtr[2];
            // Some code. Assumptions:
            // PrefixInfoPrefixLength <= 128
            // Other, less interesting assumptions about PrefixInfoPrefixLength

        case 25: // Recursive DNS Server Option
            Ipv6pUpdateRDNSS(..., NetBuffer, ...);
            AdvanceBuffer = FALSE;

        case 31: // DNS Search List Option
            Ipv6pUpdateDNSSL(..., NetBuffer, ...);
            AdvanceBuffer = FALSE;

        if (AdvanceBuffer)
            NetBuffer->DataOffset += OptionLenInBytes;
            NetBuffer->DataLength -= OptionLenInBytes;
            // Other adjustments for NetBuffer...

    // More code...

As can be seen from the code, only 6 option types are supported in the first loop, the others are ignored. In any case, each header is skipped precisely according to the Length field.

Even less options, 4, are supported in the second loop. And similarly to the first loop, each header is skipped precisely according to the Length field, but this time with two exceptions: types 24 (the Route Information Option) and 25 (Recursive DNS Server Option) have functions which adjust the network buffer pointers by themselves, creating an opportunity for inconsistencies. 

That’s exactly what is happening with this bug – the Ipv6pUpdateRDNSS function doesn’t adjust the network buffer pointers as expected when the length field is even.

Breaking assumptions

Essentially, this bug allows us to break the assumptions made by the second loop that are supposed to be verified in the first loop. The only option types that are relevant are the 4 types which appear in both loops, that’s also why we didn’t include the other 2 in the code of the first loop. One such assumption is the value of the length field, and that’s how the buffer overflow POC works, but let’s revisit them all and see what can be achieved.

  • Option type 3 – Prefix Information
    • The option structure size must be 0x20 bytes. Breaking this assumption is what allows us to trigger the stack overflow, by providing a larger option structure. We can also provide a smaller structure, but that doesn’t have much value in this case.
    • The Prefix Length field value must be at most 128. Breaking this assumption allows us to set the field to an invalid value in the range of 129-255. This can indeed be used to cause an out-of-bounds data write, but in all such cases that we could find, the out-of-bounds write happens on the stack in a location which is overridden later anyway, so causing such out-of-bounds writes has no practical value.

      For example, one such out-of-bounds write happens in tcpip!Ipv6pMakeRouteKey, called by tcpip!IppValidateSetAllRouteParameters.
  • Option type 24 – Route Information Option
    • The option structure size must not be larger than 0x18 bytes. Same implications as for option type 3.
    • The Prefix Length field value must be at most 128. Same implications as for option type 3.
    • The Prefix Length field value must fit the structure option size. That isn’t really interesting since any value in the range 0-128 is handled correctly. The worst thing that could happen here is a small out-of-bounds read.
  • Option type 25 – Recursive DNS Server Option
    • The option structure size must not be smaller than 0x18 bytes. This isn’t interesting, since the size must be at least 8 bytes anyway (the length field is verified to be larger than zero in both loops), and any such structure is handled correctly, even though a size of 8-bytes is not valid according to the specification.
    • The option structure size must be in the form of 8+n*16 bytes. This check was added after fixing CVE-2020-16898.
  • Option type 31 – DNS Search List Option
    • The option structure size must not be smaller than 0x10 bytes. Same implications as for option type 25.

As you can see, there was a slight chance of doing something other than the demonstrated stack overflow by breaking the assumption of the valid prefix length value for option type 3 or 24. Even though it’s literally about smuggling a single bit, sometimes that’s enough. But it looks like this time we weren’t that lucky.

Revisiting the Stack Overflow

Before giving up, we took a closer look at the stack. The POCs that we’ve seen are overriding the stack such that the stack cookie (the __security_cookie value) is overridden, causing a system crash before the function returns.

We checked whether overriding anything on the stack can help achieve code execution before the function returns. That can be a local variable in the “Local variables (2)” space, or any variable in the previous frames that might be referenced inside the function. Unfortunately, we came to the conclusion that all the variables in the “Local variables (2)” space are output buffers that are modified before access, and no data from the previous frames is accessed.


We conclude with high confidence that CVE-2020-16898 is not exploitable without an additional vulnerability. It is possible that we may have missed something. Any insights / feedback is welcome. Even though we weren’t able to exploit the bug, we enjoyed the research, and we hope that you enjoyed this writeup as well.

Hear the news first

  • Only essential content
  • New vulnerabilities & announcements
  • News from ZecOps Research Team

Your subscription request to ZecOps Blog has been successfully sent.
We won’t spam, pinky swear 🤞