package diff.strazzere.anti.emulator;
import android.content.Context;
import android.telephony.TelephonyManager;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import diff.strazzere.anti.common.Property;
import diff.strazzere.anti.common.Utilities;
import diff.strazzere.anti.debugger.FindDebugger;
/**
* Class used to determine functionality specific to the Android QEmu.
*
* @author tstrazzere
*/
public class FindEmulator {
// Need to check the format of these
// Android emulator support up to 16 concurrent emulator
// The console of the first emulator instance running on a given
// machine uses console port 5554
// Subsequent instances use port numbers increasing by two
private static String[] known_numbers = {
"15555215554", // Default emulator phone numbers + VirusTotal
"15555215556", "15555215558", "15555215560", "15555215562", "15555215564", "15555215566",
"15555215568", "15555215570", "15555215572", "15555215574", "15555215576", "15555215578",
"15555215580", "15555215582", "15555215584",};
private static String[] known_device_ids = {"000000000000000", // Default emulator id
"e21833235b6eef10", // VirusTotal id
"012345678912345"};
private static String[] known_imsi_ids = {"310260000000000" // Default imsi id
};
private static String[] known_pipes = {"/dev/socket/qemud", "/dev/qemu_pipe"};
private static String[] known_files = {"/system/lib/libc_malloc_debug_qemu.so", "/sys/qemu_trace",
"/system/bin/qemu-props"};
private static String[] known_geny_files = {"/dev/socket/genyd", "/dev/socket/baseband_genyd"};
private static String[] known_qemu_drivers = {"goldfish"};
/**
* Known props, in the format of [property name, value to seek] if value to seek is null, then it is assumed that
* the existence of this property (anything not null) indicates the QEmu environment.
*/
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"),
// Need to double check that an "empty" string ("") returns null
new Property("ro.serialno", null)};
/**
* The "known" props have the potential for false-positiving due to interesting (see: poorly) made Chinese
* devices/odd ROMs. Keeping this threshold low will result in better QEmu detection with possible side affects.
*/
private static int MIN_PROPERTIES_THRESHOLD = 0x5;
static {
// This is only valid for arm
System.loadLibrary("anti");
}
/**
* Check the existence of known pipes used by the Android QEmu environment.
*
* @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;
}
/**
* Check the existence of known files used by the Android QEmu environment.
*
* @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;
}
/**
* Check the existence of known files used by the Genymotion environment.
*
* @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;
}
/**
* Reads in the driver file, then checks a list for 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;
}
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;
}
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;
}
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;
}
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 (e.g., 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;
}
public static boolean isOperatorNameAndroid(Context paramContext) {
String szOperatorName = ((TelephonyManager) paramContext.getSystemService(Context.TELEPHONY_SERVICE)).getNetworkOperatorName();
boolean isAndroid = szOperatorName.equalsIgnoreCase("android");
return isAndroid;
}
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;
}
public static boolean hasEmulatorAdb() {
try {
return FindDebugger.hasAdbInEmulator();
} catch (Exception exception) {
exception.printStackTrace();
return false;
}
}
/**
* Will query specific system properties to try and fingerprint a QEmu environment. A minimum threshold must be met
* in order to prevent false positives.
*
* @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;
}
}