kevlar-kt / kevlar

Android Security Toolkit & Framework
https://kevlar-kt.github.io/kevlar
Apache License 2.0
74 stars 3 forks source link

[BUG] [Rooting] False-positives for certain devices #4

Closed Nek-12 closed 1 year ago

Nek-12 commented 1 year ago

Bug description

I'm seeing false positive results with some of my users for rooting checks. I have a small dataset for now, but something is not right at this point for sure.

The following devices are 100% confirmed to be false-positives:

But I've seen some strange results on the backend. Some devices previously shown as non-rooted, ended up being rooted anyways after some time (I run checks periodically in the background). I'm investigating into this as this may be an issue on my side. My hypothesis for now is that there's something intermittent that may affect the testing results.

Here's the KevlarRooting configuration:

KevlarRooting {
    targets {
        root()
        magisk()
        busybox()
        toybox()
        xposed()
    }

    status {
        emulator()
        testKeys()
        selinux {
            flagPermissive()
        }
    }

    allowRootCheck()
}

Out of all those, the selinux permissive flag looks the most suspicious. I think that some devices may have selinux permissive set to true. But the emulator() flag is my second best guess. My app doesn't have the QUERY_ALL_PACKAGES permission so the magisk/busybox/toybox/xposed checks may be useless.

Reproduction steps

Unable to reproduce as all of my devices are rooted and/or emulators

I will update this issue as I get more data. I expect no more than the average amount of users to be rooted which is 7.6% according to Kaspersky lab research data for 2017. (adding some probability margins here and there)

UPD1: I just learned that bootloader is unlocked out of the box on Redmi Note 8 Pro, but the user says they don't have magisk installed. Running getenforce on the target device yields command not found

Nek-12 commented 1 year ago

More information about one of the target devices:

Manufacturer: Xiaomi
Device: Begonia
Hardware: mt6785
Fingerprint: Redmi/begonia:9/PPR1.180610.011/V11.0.2.0PGGMIXM:user/release-keys
API 28
MIUI 9 - V11
Bootloader: unknown (??)
No root management apps on device
Selinux: unable to determine

su in the terminal fails

cioccarellia commented 1 year ago

The emulator check returns a DetectableSystemStatus.EMULATOR, but that doesn't mean your device is rooted. That would be if you received a DetectableSystemTarget.ROOT.

Which DetectableSystemStatus / DetectableSystemTarget entries are you getting back in your failed StatusRootingAttestation / TargetRootingAttestation?

If they are not rooted, and you get back a DetectableSystemStatus.ROOT then we might have a problem, but I doubt that's the case.

From how it was built, the idea of Kevlar was reducing false positives as low as possible. Thus, if something is detected to make kevlar think that the device is rooted, it is more than likely that there is a su binary responding to the probes, since the checks are additive. The issue is always that we may catch less that what there might be, but never more. That's the difference between approximate and unreliable.

Try to remove allowRootCheck() and see if anything changes. It would be helpful to know if those devices have the su binary installed, if so where and its details, as it is what kevlar checks for.

The QUERY_ALL_PACKAGES is for the antipiracy module, not for the rooting. There are no root-managing app checks, as those are often inconclusive. It's like testing for symptoms rather that the disease.

Also, I see you added everything possible in your configuration, but in theory you should only add what you care about. If you don't mind the selinux status of the device, then it's better to leave it off, so that the data that you do get back is what should make you alert.

Nek-12 commented 1 year ago

The emulator check returns a DetectableSystemStatus.EMULATOR, but that doesn't mean your device is rooted.

Usually, emulators are indeed rooted, easily rootable or otherwise compromised. I actually just fail the check on emulator detection, but your comment made me reconsider my approach and I removed that check. Thank you.

Which DetectableSystemStatus / DetectableSystemTarget entries are you getting back in your failed StatusRootingAttestation / TargetRootingAttestation?

Providing logs to you or knowing which statuses are failing is proving difficult as I don't have access to logs on user devices in production and do not store such granular information on my app's servers.

Thus, if something is detected to make kevlar think that the device is rooted, it is more than likely that there is a su binary responding to the probes, since the checks are additive.

Like I said, I asked a user to run su in a terminal and the response was command not found. However I don't think only the binary dumper checks may be at fault here. I assume checks failed when any of the attestation conditions are unmet. I will try to fix the problem by removing selinux permissive flag. I suspect some oems/rom maintainers mess around with selinux, I used to mess too, and although it's a serious security risk, for purposes of my application this isn't a critical issue.

The QUERY_ALL_PACKAGES is for the antipiracy module, not for the rooting.

Sorry, my mistake, I confused how these two modules work. Just thought you also detect root by its respective companion app. However I think that root-managing apps should be checked. Magisk is gonna be hard indeed, but for people that use magisk, they sometimes also use Riru, Exposed companion, GravityBox, TitaniumBackup or some other app that requires root. I would like to be able to check for these too, but in my opinion we should only add here apps that are definitively root-only (i.e. no non-root functionality users might have used that caused the check to fail).

cioccarellia commented 1 year ago

Providing logs to you or knowing which statuses are failing is proving difficult as I don't have access to logs on user devices in production and do not store such granular information on my app's servers.

Makes sense, the idea indeed is to compute data locally and act basing on the conditions you can / can not accept your app to run in.

I assume checks failed when any of the attestation conditions are unmet. I will try to fix the problem by removing selinux permissive flag.

The binary dumper output maps only to DetectableSystemTarget.ROOT. Therefore, if running su in the terminal does not produce any notable result, then the binary dumper is probably not at fault. For instance, Xposed checks will return a DetectableSystemTarget.XPOSED in the attestation.

It is your job to understand what went wrong in a failed attestation, and act accordingly (in compliance with your security environment requirements). In this case maybe you are assuming that the check fails if any of the attestation component has failed, which is a bit strict. I should do a better job at explaining this in the docs, will work on it.

I suspect some oems/rom maintainers mess around with selinux, I used to mess too, and although it's a serious security risk, for purposes of my application this isn't a critical issue.

Yes, indeed the selinux() flag is often at fault, since many devices do not have a fully compliant SELinux configuration. But it usually doesn't affect your application like some other elements (namely root access) may: it is only for very dedicated uses. Furthermore, enabling flagPermissive() will further restrict the set of allowed configurations, as even permissive selinux status will not be tolerated.

I would like to be able to check for these too, but in my opinion we should only add here apps that are definitively root-only (i.e. no non-root functionality users might have used that caused the check to fail).

Exactly! That is actually a hard thing to do, since we don't want faithful/genuine users to fail checks meant to catch root users. Plus, detecting piracy is more or less ok since it is something aimed exactly at adversarial apps, but targeting third-party apps only as a means to infer root access is questionable at best. It would be quite straightforward to implement (just like in antipiracy), but it looks like something a financial application would want to implement themselves.