I just read some open source projects/articles/papers about Detect Android Emulator. The ones I read are actually methods proposed in 2013 and 2014. Most of them detect some environmental properties and check some files, but in fact, the detection ideas are not limited to this. Some are very direct to detect qemu, while other methods are indirect, such as detecting adb, detecting ptrace, etc. The ideas are also very flexible. *** I have seen some suggestions to detect by using the actual differences between simulated CPUs such as QEMU and physical CPUs (task scheduling differences), the differences between simulated sensors and physical sensors, cache differences, etc. Compared with detecting environmental properties, the detection effect will be much better. [[228598]] Below I will list some methods/ideas/codes proposed in various materials for everyone to communicate and learn. QEMU Properties - public class Property {
- public String name ;
- public String seek_value;
-
- public Property(String name , String seek_value) {
- this.name = name ;
- this.seek_value = seek_value;
- }
- }
- /**
- * Known attributes, in the format of [attribute name, attribute value], used to determine whether the current environment is QEMU
- */
- private static Property[] known_props = {new Property( "init.svc.qemud" , null ),
- new Property( "init.svc.qemu-props" , null ), new Property( "qemu.hw.mainkeys" , null ),
- new Property( "qemu.sf.fake_camera" , null ), new Property( "qemu.sf.lcd_density" , null ),
- new Property( "ro.bootloader" , "unknown" ), new Property( "ro.bootmode" , "unknown" ),
- new Property( "ro.hardware" , "goldfish" ), new Property( "ro.kernel.android.qemud" , null ),
- new Property( "ro.kernel.qemu.gles" , null ), new Property( "ro.kernel.qemu" , "1" ),
- new Property( "ro.product.device" , "generic" ), new Property( "ro.product.model" , "sdk" ),
- new Property( "ro.product.name" , "sdk" ),
- new Property( "ro.serialno" , null )};
- /**
- * A threshold, because the so-called "known" simulator properties are not completely accurate, there may be false positive results, so maintaining a certain threshold can make the detection effect better
- */
- private static int MIN_PROPERTIES_THRESHOLD = 0x5;
- /**
- * Try to detect the QEMU environment by querying the specified system properties, and then compare the detection results with the threshold.
- *
- * @param context A {link Context} object for the Android application.
- * @ return {@code true } if enough properties where found to exist or {@code false } if not .
- */
- public boolean hasQEmuProps(Context context) {
- int found_props = 0;
-
- for (Property property : known_props) {
- String property_value = Utilities.getProp(context, property. name );
- // See if we expected just a non- null
- if ((property.seek_value == null ) && (property_value != null )) {
- found_props++;
- }
- // See if we expected a value to seek
- if ((property.seek_value != null ) && (property_value.indexOf(property.seek_value) != -1)) {
- found_props++;
- }
-
- }
-
- if (found_props >= MIN_PROPERTIES_THRESHOLD) {
- return true ;
- }
-
- return false ;
- }
These are attributes that are compared based on some experience and features. I will not explain the attributes here and some of the subsequent file attributes in detail. Device ID - private static String[] known_device_ids = { "000000000000000" , // Default emulator id
- "e21833235b6eef10" , // VirusTotal id
- "012345678912345" };
- public static boolean hasKnownDeviceId(Context context) {
- TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
-
- String deviceId = telephonyManager.getDeviceId();
-
- for (String known_deviceId : known_device_ids) {
- if (known_deviceId.equalsIgnoreCase(deviceId)) {
- return true ;
- }
-
- }
- return false ;
- }
Default Number - private static String[] known_numbers = {
- "15555215554" , // emulator default phone number + VirusTotal
- "15555215556" , "15555215558" , "15555215560" , "15555215562" , "15555215564" , "15555215566" ,
- "15555215568" , "15555215570" , "15555215572" , "15555215574" , "15555215576" , "15555215578" ,
- "15555215580" , "15555215582" , "15555215584" ,};
- public static boolean hasKnownPhoneNumber(Context context) {
- TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
-
- String phoneNumber = telephonyManager.getLine1Number();
-
- for (String number : known_numbers) {
- if (number.equalsIgnoreCase(phoneNumber)) {
- return true ;
- }
-
- }
- return false ;
- }
IMSI - private static String[] known_imsi_ids = { "310260000000000" // Default IMSI number
- };
- public static boolean hasKnownImsi(Context context) {
- TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
- String imsi = telephonyManager.getSubscriberId();
-
- for (String known_imsi : known_imsi_ids) {
- if (known_imsi.equalsIgnoreCase(imsi)) {
- return true ;
- }
- }
- return false ;
- }
Build Class - public static boolean hasEmulatorBuild(Context context) {
- String BOARD = android.os.Build.BOARD; // The name of the underlying board, like "unknown" .
- // This appears to occur often on real hardware... that's sad
- // String BOOTLOADER = android.os.Build.BOOTLOADER; // The system bootloader version number.
- String BRAND = android.os.Build.BRAND; // The brand (eg, carrier) the software is customized for , if any .
- // "generic"
- String DEVICE = android.os.Build.DEVICE; // The name of the industrial design. "generic"
- String HARDWARE = android.os.Build.HARDWARE; // The name of the hardware ( from the kernel command line or
- // /proc). "goldfish"
- String MODEL = android.os.Build.MODEL; // The end - user -visible name for the end product. "sdk"
- String PRODUCT = android.os.Build.PRODUCT; // The name of the overall product.
- if ((BOARD.compareTo( "unknown" ) == 0) /* || (BOOTLOADER.compareTo( "unknown" ) == 0) */
- || (BRAND.compareTo( "generic" ) == 0) || (DEVICE.compareTo( "generic" ) == 0)
- || (MODEL.compareTo( "sdk" ) == 0) || (PRODUCT.compareTo( "sdk" ) == 0)
- || (HARDWARE.compareTo( "goldfish" ) == 0)) {
- return true ;
- }
- return false ;
- }
Operator Name - public static boolean isOperatorNameAndroid(Context paramContext) {
- String szOperatorName = ((TelephonyManager) paramContext.getSystemService(Context.TELEPHONY_SERVICE)).getNetworkOperatorName();
- boolean isAndroid = szOperatorName.equalsIgnoreCase( "android" );
- return isAndroid;
- }
QEMU Driver - private static String[] known_qemu_drivers = { "goldfish" };
- /**
- * Read the driver file and check whether it contains known qemu drivers
- *
- * @ return {@code true } if any known drivers where found to exist or {@code false } if not .
- */
- public static boolean hasQEmuDrivers() {
- for (File drivers_file : new File[]{new File( "/proc/tty/drivers" ), new File( "/proc/cpuinfo" )}) {
- if (drivers_file.exists() && drivers_file.canRead()) {
- // We don't care to read much past things since info we care about should be inside here
- byte[] data = new byte[1024];
- try {
- InputStream is = new FileInputStream(drivers_file);
- is . read ( data );
- is . close ();
- } catch (Exception exception) {
- exception.printStackTrace();
- }
-
- String driver_data = new String(data);
- for (String known_qemu_driver : FindEmulator.known_qemu_drivers) {
- if (driver_data.indexOf(known_qemu_driver) != -1) {
- return true ;
- }
- }
- }
- }
-
- return false ;
- }
QEMU Files - private static String[] known_files = { "/system/lib/libc_malloc_debug_qemu.so" , "/sys/qemu_trace" ,
- "/system/bin/qemu-props" };
- /**
- * Check if known QEMU environment files exist
- *
- * @ return {@code true } if any files where found to exist or {@code false } if not .
- */
- public static boolean hasQEmuFiles() {
- for (String pipe : known_files) {
- File qemu_file = new File(pipe);
- if (qemu_file.exists()) {
- return true ;
- }
- }
-
- return false ;
- }
Genymotion Files - private static String[] known_geny_files = { "/dev/socket/genyd" , "/dev/socket/baseband_genyd" };
- /**
- * Check if there is a known Genemytion environment file
- *
- * @ return {@code true } if any files where found to exist or {@code false } if not .
- */
- public static boolean hasGenyFiles() {
- for (String file : known_geny_files) {
- File geny_file = new File(file);
- if (geny_file.exists()) {
- return true ;
- }
- }
-
- return false ;
- }
QEMU pipes - private static String[] known_pipes = { "/dev/socket/qemud" , "/dev/qemu_pipe" };
- /**
- * Check if there are any known pipes used by QEMU
- *
- * @ return {@code true } if any pipes where found to exist or {@code false } if not .
- */
- public static boolean hasPipes() {
- for (String pipe : known_pipes) {
- File qemu_socket = new File(pipe);
- if (qemu_socket.exists()) {
- return true ;
- }
- }
-
- return false ;
- }
Setting breakpoints - static {
- // This is only valid for arm
- System.loadLibrary( "anti" );
- }
- public native static int qemuBkpt();
-
- public static boolean checkQemuBreakpoint() {
- boolean hit_breakpoint = false ;
-
- // Potentially you may want to see if this is a specific value
- int result = qemuBkpt();
-
- if (result > 0) {
- hit_breakpoint = true ;
- }
-
- return hit_breakpoint;
- }
The following is the corresponding C++ code - void handler_sigtrap( int signo) {
- exit(-1);
- }
-
- void handler_sigbus( int signo) {
- exit(-1);
- }
-
- int setupSigTrap() {
- // BKPT throws SIGTRAP on nexus 5 / oneplus one ( and most devices)
- signal(SIGTRAP, handler_sigtrap);
- // BKPT throws SIGBUS on nexus 4
- signal(SIGBUS, handler_sigbus);
- }
-
- // This will cause a SIGSEGV on some QEMU or be properly respected
- int tryBKPT() {
- __asm__ __volatile__ ( "bkpt 255" );
- }
-
- jint Java_diff_strazzere_anti_emulator_FindEmulator_qemuBkpt(JNIEnv* env, jobject jObject) {
-
- pid_t child = fork();
- int child_status, status = 0;
-
- if(child == 0) {
- setupSigTrap();
- tryBKPT();
- } else if(child == -1) {
- status = -1;
- } else {
-
- int timeout = 0;
- int i = 0;
- while ( waitpid(child, &child_status, WNOHANG) == 0 ) {
- sleep(1);
- // Time could be adjusted here, though in my experience if the child has not returned instantly
- // then something has gone wrong and it is an emulated device
- if(i++ == 1) {
- timeout = 1;
- break;
- }
- }
-
- if(timeout == 1) {
- // Process timed out - likely an emulated device and child is frozen
- status = 1;
- }
-
- if ( WIFEXITED(child_status) ) {
- // The child process exits normally
- status = 0;
- } else {
- // Didn't exit properly - very likely an emulator
- status = 2;
- }
-
- // Ensure child is dead
- kill(child, SIGKILL);
- }
-
- return status;
- }
My description here may not be accurate, because I have not found relevant information. I can only explain it based on my own understanding: SIGTRAP is a signal generated when the debugger sets a breakpoint. It can be triggered on most phones such as nexus5 or OnePlus phones. SIGBUS is a bus error. The pointer may access a valid address, but the bus cannot be used due to data misalignment and other reasons. It can be triggered on nexus4 phones. Bkpt is the breakpoint instruction of arm. This is an issue that qemu has raised. qemu will crash due to the SIGSEGV signal. The author wants to use this crash to detect qemu. If the program does not exit normally or is frozen, it can be determined that it is likely in the emulator. ADB - public static boolean hasEmulatorAdb() {
- try {
- return FindDebugger.hasAdbInEmulator();
- } catch (Exception exception) {
- exception.printStackTrace();
- return false ;
- }
- }
isUserAMonkey() - public static boolean hasEmulatorAdb() {
- try {
- return FindDebugger.hasAdbInEmulator();
- } catch (Exception exception) {
- exception.printStackTrace();
- return false ;
- }
- }
This is actually used to detect whether the current operation is requested by the user or the script. isDebuggerConnected() - /**
- * Believe it or not, there are many hardening programs that use this method...
- */
- public static boolean isBeingDebugged() {
- return Debug.isDebuggerConnected();
- }
This method is used to detect debugging and determine whether a debugger is connected. ptrace - private static String tracerpid = "TracerPid" ;
- /**
- * Alibaba uses this to detect whether the application process is being tracked
- *
- * Easy to circumvent, the usage is to create a thread to check every 3 seconds, if detected, the program will crash
- *
- * @return
- * @throws IOException
- */
- public static boolean hasTracerPid() throws IOException {
- BufferedReader reader = null ;
- try {
- reader = new BufferedReader(new InputStreamReader(new FileInputStream( "/proc/self/status" )), 1000);
- String line;
-
- while ((line = reader.readLine()) != null ) {
- if (line.length() > tracerpid.length()) {
- if ( line.substring (0, tracerpid.length()).equalsIgnoreCase(tracerpid)) {
- if ( Integer .decode(line.substring ( tracerpid.length() + 1).trim()) > 0) {
- return true ;
- }
- break;
- }
- }
- }
-
- } catch (Exception exception) {
- exception.printStackTrace();
- finally
- reader.close () ;
- }
- return false ;
- }
This method is to check the TracerPid item in /proc/self/status. This item defaults to 0 when there is no tracing, and will be modified to the corresponding pid when a program is tracing. Therefore, if TracerPid is not equal to 0, it can be considered to be in the simulator environment. TCP Connection - public static boolean hasAdbInEmulator() throws IOException {
- boolean adbInEmulator = false ;
- BufferedReader reader = null ;
- try {
- reader = new BufferedReader(new InputStreamReader(new FileInputStream( "/proc/net/tcp" )), 1000);
- String line;
- // Skip column names
- reader.readLine();
-
- ArrayList<tcp> tcpList = new ArrayList<tcp>();
-
- while ((line = reader.readLine()) != null ) {
- tcpList. add (tcp. create (line.split( "\\W+" )));
- }
-
- reader.close () ;
-
- // Adb is always bounce to 0.0.0.0 - though the port can change
- // real devices should be != 127.0.0.1
- int adbPort = -1;
- for (tcp tcpItem : tcpList) {
- if (tcpItem.localIp == 0) {
- adbPort = tcpItem.localPort;
- break;
- }
- }
-
- if (adbPort != -1) {
- for (tcp tcpItem : tcpList) {
- if ((tcpItem.localIp != 0) && (tcpItem.localPort == adbPort)) {
- adbInEmulator = true ;
- }
- }
- }
- } catch (Exception exception) {
- exception.printStackTrace();
- finally
- reader.close () ;
- }
-
- return adbInEmulator;
- }
-
- public static class tcp {
-
- public int id;
- public long localIp;
- public int localPort;
- public int remoteIp;
- public int remotePort;
-
- static tcp create (String[] params) {
- return new tcp(params[1], params[2], params[3], params[4], params[5], params[6], params[7], params[8],
- params[9], params[10], params[11], params[12], params[13], params[14]);
- }
-
- public tcp(String id, String localIp, String localPort, String remoteIp, String remotePort, String state,
- String tx_queue, String rx_queue, String tr, String tm_when, String retrnsmt, String uid,
- String timeout, String inode) {
- this.id = Integer .parseInt(id, 16);
- this.localIp = Long.parseLong(localIp, 16);
- this.localPort = Integer .parseInt(localPort, 16);
- }
- }
This method is to determine whether adb exists by reading the information of /proc/net/tcp. For example, the information of the real machine is 0: 4604D20A:B512 A3D13AD8..., and the corresponding information on the simulator is 0: 00000000:0016 00000000:0000, because adb is usually reflected to the IP address 0.0.0.0. Although the port may change, it is indeed feasible. TaintDroid - public static boolean hasPackageNameInstalled(Context context, String packageName) {
- PackageManager packageManager = context.getPackageManager();
-
- // In theory, if the package installer does not throw an exception, package exists
- try {
- packageManager.getInstallerPackageName(packageName);
- return true ;
- } catch (IllegalArgumentException exception) {
- return false ;
- }
- }
- public static boolean hasAppAnalysisPackage(Context context) {
- return Utilities.hasPackageNameInstalled(context, "org.appanalysis" );
- }
- public static boolean hasTaintClass() {
- try {
- Class.forName( "dalvik.system.Taint" );
- return true ;
- }
- catch (ClassNotFoundException exception) {
- return false ;
- }
- }
This is relatively simple. It is to determine whether the TaintDroid taint analysis tool is installed by detecting the package name and the Taint class. In addition, some member variables of TaintDroid can also be detected. eth0 - private static boolean hasEth0Interface() {
- try {
- for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
- NetworkInterface intf = en.nextElement();
- if (intf.getName().equals( "eth0" ))
- return true ;
- }
- } catch (SocketException ex) {
- }
- return false ;
- }
Check whether the eth0 network card exists. sensor Mobile phones are equipped with a variety of sensors, but they essentially output values based on information collected from the environment, so it is very challenging to simulate sensors. These sensors provide new opportunities for identifying mobile phones and simulators. For example, in the paper Rage Against the Virtual Machine: Hindering Dynamic Analysis of Android Malware, the authors tested the accelerometer of the Android emulator and found that the sensors on the Android emulator would produce the same value at the same time interval (the observed result was 0.8s, with a standard deviation of 0.003043). Obviously, this is impossible for real-world sensors. So we can first register a sensor listener. If the registration fails, it may be in the simulator (excluding the possibility that the actual device does not support the sensor). If the registration is successful, then check the onSensorChanged callback method. If the sensor value or time interval observed in the process of calling this method continuously is the same, then it can be determined that it is in the simulator environment. QEMU Task Scheduling For performance reasons, QEMU does not actively update the program counter (PC) every time it executes an instruction. Since the translated instructions are executed locally, increasing the PC requires additional instructions and incurs overhead. Therefore, QEMU only updates the program counter when executing instructions that interrupt the linear execution process (such as branch instructions). This means that if a scheduling event occurs during the execution of some basic blocks, there is no way to restore the PC before scheduling. For this reason, QEMU only schedules events after executing basic blocks, and never during execution. As shown in the figure above, because scheduling can occur at any time, a large number of scheduling points will be observed in a non-simulator environment. In a simulator environment, only specific scheduling points can be seen. SMC Identification Because QEMU tracks code page changes, there is a novel way to instrument QEMU using Self-Modifying Code (SMC) to cause execution flow changes between the emulator and the real device. ARM processors contain two different caches, one for instruction access (I-Cache) and the other for data access (D-Cache). But Harvard architectures like ARM do not guarantee consistency between I-Cache and D-Cache. Therefore, it is possible for the CPU to execute an old (perhaps invalid) piece of code after a new piece of code has been written to the main memory. This problem can be solved by forcing the two caches to be consistent, which has two steps: - Clean up the main memory so that the newly written code in the D-Cache can be moved into the main memory
- Invalidate the I-Cache so that it can be refilled with new contents from main memory
In native Android code, you can use the cacheflush function, which completes the above operation through system calls. Identify the code, using a memory with read-write permissions, which contains the code of two different functions f1 and f2. These two functions are actually very simple. They simply append their respective function names to the end of a global string variable. These two functions will be interleaved in the loop, so that the function call sequence can be inferred from the resulting string. As mentioned earlier, we call cacheflush to synchronize the cache. Running the code on a real device and the simulator gives the same results - each execution produces a consistent sequence of function calls. Next, we remove the call to cacheflush and perform the same operation. In the actual device, we observe a random sequence of function calls each time we run it. This is also because the I-Cache may contain some old instructions, and the cache is out of sync each time it is called, as mentioned above. This does not happen in the simulator environment, and the function call sequence will be exactly the same as before cacheflush was removed, that is, the cache is consistent before each function call. This is because QEMU tracks modifications on code pages and ensures that the generated code always matches the target instructions in memory, so QEMU abandons the previous version of the code translation and regenerates new code. Conclusion You may think there are enough detection methods here, but I have only read the data from 2013 and 2014. The data from recent years has not been included. ***I will integrate these detection methods into a mind map (see attachment) for everyone to see, welcome to communicate with me and guide me Reference Links strazzere/anti-emulator: ***Published at HitCon in 2013, it proposed some methods and ideas for detecting virtual machines. It should be the pioneering work of Android emulator detection. This article is mainly based on this repository. Rage Against the Virtual Machine: Hindering Dynamic Analysis of Android Malware: Detection through task scheduling and identification using SMC are both referenced from this paper. This paper and the following paper are very valuable for reference and are worth reading. Evading Android Runtime Analysis via Sandbox Detection: This paper proposes a lot of methods and ideas for detecting the Android runtime environment. It is rich in content and very comprehensive, and it is also worth reading. CalebFenton/AndroidEmulatorDetect: This repository actually integrates the detection methods and codes in some articles and repositories, and it is not comprehensive, but it does provide a lot of reference links, and I followed the clues. How can I detect when an Android application is running in the emulator? Netizens have given many solutions. But in fact, they are not comprehensive, and are just the tip of the iceberg in emulator detection. After all, there are so many places that can be detected. Using the Task Scheduling Feature to Detect Android Emulators |