Normal view

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

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.

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.

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));

Output:

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";
            default:
                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.  

PropertySpecs from siteSettings and HdfUtil.javaCPU-Z AppReal values
Android10.010.010.06.0 (getprop)
RAM12GB8GB8192 MB8388608 K
(/proc/meminfo)
ROM512G512G512G8G (pushing files until no space left on the device)
CPUMTK6799 10 coresMTK6799 10 coresMTK6799MTK6753(/proc/cpuinfo)
Screen resolution2320×12802320×1280N/A720×1520 (wm size)
Security patchN/AJanuary 5, 2019January 5, 2019January 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.

❌
❌