SafetyNetApi.attest
method (which returns a JWS message with the Attestation Result) and then check the following fields:ctsProfileMatch
: If 'true', the device profile matches one of Google's listed devices.basicIntegrity
: If 'true', the device running the app likely hasn't been tampered with.nonces
: To match the response to its request.timestampMs
: To check how much time has passed since you made the request and you got the response. A delayed response may suggest suspicious activity.apkPackageName
, apkCertificateDigestSha256
, apkDigestSha256
: Provide information about the APK, which is used to verify the identity of the calling app. These parameters are absent if the API cannot reliably determine the APK information.basicIntegrity
to help developers determine the integrity of a device. As the API evolved, Google introduced a new, stricter check whose results appear in a value called ctsProfileMatch
, which allows developers to more finely evaluate the devices on which their app is running.basicIntegrity
gives you a signal about the general integrity of the device and its API. Many Rooted devices fail basicIntegrity
, as do emulators, virtual devices, and devices with signs of tampering, such as API hooks.ctsProfileMatch
gives you a much stricter signal about the compatibility of the device. Only unmodified devices that have been certified by Google can pass ctsProfileMatch
. Devices that will fail ctsProfileMatch
include the following:basicIntegrity
SafetyNetApi.attest
apkPackageName
, apkCertificateDigestSha256
and apkDigestSha256
) only if the value of ctsProfileMatch
is true.verify
method only validates that the JWS message was signed by SafetyNet. It doesn't verify that the payload of the verdict matches your expectations. As useful as this service may seem, it is designed for test purposes only, and it has very strict usage quotas of 10,000 requests per day, per project which will not be increased upon request. Hence, you should refer SafetyNet Verification Samples and implement the digital signature verification logic on your server in a way that it doesn't depend on Google's servers.SafetyNetApi.attest
quota and getting attestation errors, you should build a system that monitors your usage of the API and warns you well before you reach your quota so you can get it increased. You should also be prepared to handle attestation failures because of an exceeded quota and avoid blocking all your users in this situation. If you are close to reaching your quota, or expect a short-term spike that may lead you to exceed your quota, you can submit this form to request short or long-term increases to the quota for your API key. This process, as well as the additional quota, is free of charge.SafetyNetApi.attest
API into the app.su
is on the PATH also works:stat
system call to retrieve information about a file and returns "1" if the file exists.su
and other commandssu
exists is attempting to execute it through the Runtime.getRuntime.exec
method. An IOException will be thrown if su
is not on the PATH. The same method can be used to check for other programs often found on rooted devices, such as busybox and the symbolic links that typically point to it.daemonsu
, so the presence of this process is another sign of a rooted device. Running processes can be enumerated with the ActivityManager.getRunningAppProcesses
and manager.getRunningServices
APIs, the ps
command, and browsing through the /proc
directory. The following is an example implemented in rootinspector:strace
, and/or kernel modules to find out what the app is doing. You'll usually see all kinds of suspect interactions with the operating system, such as opening su
for reading and obtaining a list of processes. These interactions are surefire signs of root detection. Identify and deactivate the root detection mechanisms, one at a time. If you're performing a black box resilience assessment, disabling the root detection mechanisms is your first step.su
binary is enough to defeat root detection (try not to break your environment though!)./proc
to prevent reading of process lists. Sometimes, the unavailability of /proc
is enough to bypass such checks.ro.debuggable
system property which enables debugging for all apps. Let's look at a few things developers do to detect and disable JDWP debuggers.android:debuggable
attribute. This flag in the Android Manifest determines whether the JDWP thread is started for the app. Its value can be determined programmatically, via the app's ApplicationInfo
object. If the flag is set, the manifest has been tampered with and allows debugging.isDebuggerConnected
from the android.os.Debug
class to determine whether a debugger is connected.Debug.threadCpuTimeNanos
indicates the amount of time that the current thread has been executing code. Because debugging slows down process execution, you can use the difference in execution time to guess whether a debugger is attached.DvmGlobals
structure. The global variable gDvm holds a pointer to this structure. DvmGlobals
contains various variables and pointers that are important for JDWP debugging and can be tampered with.JdwpSocketState
and JdwpAdbState
, which handle JDWP connections via network sockets and ADB, respectively. You can manipulate the behavior of the debugging runtime by overwriting the method pointers in the associated vtables (archived).jdwpAdbState::ProcessIncoming
with the address of JdwpAdbState::Shutdown
. This will cause the debugger to disconnect immediately.ptrace
system call is used to observe and control the execution of a process (the tracee) and to examine and change that process' memory and registers. ptrace
is the primary way to implement system call tracing and breakpoint debugging in native code. Most JDWP anti-debugging tricks (which may be safe for timer-based checks) won't catch classical debuggers based on ptrace
and therefore, many Android anti-debugging tricks include ptrace
, often exploiting the fact that only one debugger at a time can attach to a process.ptrace
to attach to the process. From this moment on, if you inspect the status file of the debugged process (/proc/<pid>/status
or /proc/self/status
), you will see that the "TracerPid" field has a value different from 0, which is a sign of debugging.Remember that this only applies to native code. If you're debugging a Java/Kotlin-only app the value of the "TracerPid" field should be 0.
IsDebuggerAttached
method. However, if you prefer to include this check as part of your Java/Kotlin code you can refer to this Java implementation of the hasTracerPid
method from Tim Strazzere's Anti-Emulator project./proc
filesystem, such as TracerPID in /proc/pid/status
.fork
, we launch in the parent an extra thread that continually monitors the child's status. Depending on whether the app has been built in debug or release mode (which is indicated by the android:debuggable
flag in the manifest), the child process should do one of the following things:waitpid(child_pid)
should never return. If it does, something is fishy and we would kill the whole process group._exit
with NOPs and hooking the function _exit
in libc.so
). At this point, we have entered the proverbial "arms race": implementing more intricate forms of this defense as well as bypassing it are always possible.isDebuggable
and isDebuggerConnected
to hide the debugger.CodeCheck
is responsible for verifying the code entered by the user. The actual check appears to occur in the bar
method, which is declared as a native method.SharedPreferences
should be protected.classes.dex
and compares it to the expected value.SharedPreferences
) or create an HMAC over a complete file that's provided by the file system.doFinal
on the HMAC with the bytecode.AndroidKeyStore
:classes.dex
and any .so libraries in the app package. Re-package and re-sign the app as described in the "Basic Security Testing" chapter, then run the app. The app should detect the modification and respond in some way. At the very least, the app should alert the user and/or terminate. Work on bypassing the defenses and answer the following questions:/proc/<pid>/maps
you'll find the frida-agent as frida-agent-64.so:Some of the following detection methods are presented in the article "The Jiu-Jitsu of Detecting Frida" by Berdhard Mueller (archived). Please refer to it for more details and for example code snippets.
getRunningServices
) and processes (ps
) searching for one whose name is "frida-server". You could also walk through the list of loaded libraries and check for suspicious ones (e.g. those including "frida" in their names).frida-server
uses the D-Bus protocol to communicate, so you can expect it to respond to D-Bus AUTH. Send a D-Bus AUTH message to every open port and check for an answer, hoping that frida-server
will reveal itself.frida-server
, but Frida offers alternative modes of operation that don't require frida-server.Runtime.getRuntime().exec
and iterate through the memory mappings listed in /proc/self/maps
or /proc/<pid>/maps
(depending on the Android version) searching for the string.It is important to note that these controls are only increasing the complexity of the reverse engineering process. If used, the best approach is to combine the controls cleverly instead of using them individually. However, none of them can assure a 100% effectiveness, as the reverse engineer will always have full access to the device and will therefore always win! You also have to consider that integrating some of the controls into your app might increase the complexity of your app and even have an impact on its performance.
build.prop
.build.prop
on a rooted Android device or modify it while compiling AOSP from source. Both techniques will allow you to bypass the static string checks above.TelephonyManager.getDeviceID
method to return an IMEI value.