/* Android IMSI-Catcher Detector | (c) AIMSICD Privacy Project * ----------------------------------------------------------- * LICENSE: http://git.io/vki47 | TERMS: http://git.io/vki4o * ----------------------------------------------------------- */ package com.secupwn.aimsicd.service; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.location.Location; import android.os.Build; import android.os.Vibrator; import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationManagerCompat; import android.telephony.CellInfo; import android.telephony.CellLocation; import android.telephony.NeighboringCellInfo; import android.telephony.PhoneStateListener; import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.TelephonyManager; import android.telephony.cdma.CdmaCellLocation; import android.telephony.gsm.GsmCellLocation; import com.secupwn.aimsicd.AndroidIMSICatcherDetector; import com.secupwn.aimsicd.BuildConfig; import com.secupwn.aimsicd.R; import com.secupwn.aimsicd.utils.RealmHelper; import com.secupwn.aimsicd.enums.Status; import com.secupwn.aimsicd.ui.activities.MainActivity; import com.secupwn.aimsicd.utils.Cell; import com.secupwn.aimsicd.utils.Device; import com.secupwn.aimsicd.utils.DeviceApi18; import com.secupwn.aimsicd.utils.Helpers; import com.secupwn.aimsicd.utils.Icon; import com.secupwn.aimsicd.utils.TinyDB; import java.util.ArrayList; import java.util.List; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import io.freefair.android.util.logging.AndroidLogger; import io.freefair.android.util.logging.Logger; import io.realm.Realm; import lombok.Cleanup; import lombok.Getter; /** * Description: Class to handle tracking of cell information * * Dependencies: * * Issues: * * * Note: The refresh rate is set in two different places: * onSharedPreferenceChanged() * loadPreferences() * * For proper TinyDB implementation use something like: * https://github.com/kcochibili/TinyDB--Android-Shared-Preferences-Turbo/issues/6 * * * * ToDo: Currently the automatic refresh rate is hard-coded to 15 seconds, * in 2 different places above. We may consider have this more transparent * in a static variable. It is also used in timerRunnable where it * defaults to 25 seconds. * * [x] Use TinyDB.java to simplify Shared Preferences usage */ public class CellTracker implements SharedPreferences.OnSharedPreferenceChangeListener { private final Logger log = AndroidLogger.forClass(CellTracker.class); @Getter public static Cell monitorCell; public static String OCID_API_KEY = null; // see getOcidKey() public static int PHONE_TYPE; // public static long REFRESH_RATE; // [s] The DeviceInfo refresh rate (arrays.xml) public static final String SILENT_SMS = "SILENT_SMS_DETECTED"; private boolean CELL_TABLE_CLEANSED; // default is FALSE for "boolean", and NULL for "Boolean". private final int NOTIFICATION_ID = 1; @Getter private final Device device = new Device(); private static TelephonyManager tm; private final SignalStrengthTracker signalStrengthTracker; private PhoneStateListener mPhoneStateListener; private SharedPreferences prefs; private TinyDB tinydb; // Used to simplify SharedPreferences usage above //===================================================== // Tracking and Alert Declarations //===================================================== @Getter private boolean monitoringCell; @Getter private boolean trackingCell; /** * Tracking Femotcell Connections * TODO: Consider REMOVAL! * * @return boolean indicating Femtocell Connection Tracking State */ @Getter private boolean trackingFemtocell; private boolean femtoDetected; private boolean changedLAC; private boolean cellIdNotInOpenDb; private boolean typeZeroSmsDetected; private boolean vibrateEnabled; private int vibrateMinThreatLevel; private LinkedBlockingQueue<NeighboringCellInfo> neighboringCellBlockingQueue; private final RealmHelper dbHelper; private Context context; public CellTracker(final Context context, SignalStrengthTracker sst) { this.context = context; this.signalStrengthTracker = sst; // Creating tinydb here to avoid: "TinyDb tinydb = new TinyDb(context);" // every time we need to use tinydb in this class. tinydb = TinyDB.getInstance(); // TelephonyManager provides system details tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); // Shared Preferences prefs = context.getSharedPreferences(AimsicdService.SHARED_PREFERENCES_BASENAME, 0); prefs.registerOnSharedPreferenceChangeListener(this); loadPreferences(); setNotification(); PHONE_TYPE = tm.getPhoneType(); // PHONE_TYPE_GSM/CDMA/SIP/NONE dbHelper = new RealmHelper(context); // Remove all but the last DBi_bts entry, after: // (a) starting CellTracker for the first time or // (b) having cleared the preferences. // Subsequent runs are prevented by a hidden boolean preference. See: loadPreferences() if (!CELL_TABLE_CLEANSED) { @Cleanup Realm realm = Realm.getDefaultInstance(); Realm.Transaction transaction = dbHelper.cleanseCellTable(); realm.executeTransactionAsync(transaction, new Realm.Transaction.OnSuccess() { @Override public void onSuccess() { SharedPreferences.Editor prefsEditor; prefsEditor = prefs.edit(); prefsEditor.putBoolean(context.getString(R.string.pref_cell_table_cleansed), true); prefsEditor.apply(); } }); } device.refreshDeviceInfo(tm, context); // Telephony Manager monitorCell = new Cell(); } /** * Description: Cell Information Monitoring * TODO: What exactly are we monitoring here?? * TODO: Is this related to the Tracking/Monitoring in the menu drawer? * * @param monitor Enable/Disable monitoring */ public void setCellMonitoring(boolean monitor) { if (monitor) { monitoringCell = true; Helpers.msgShort(context, context.getString(R.string.monitoring_cell_information)); } else { monitoringCell = false; Helpers.msgShort(context, context.getString(R.string.stopped_monitoring_cell_information)); } setNotification(); } public void stop() { if (isMonitoringCell()) { setCellMonitoring(false); } if (isTrackingCell()) { setCellTracking(false); } if (isTrackingFemtocell()) { stopTrackingFemto(); } cancelNotification(); tm.listen(cellSignalListener, PhoneStateListener.LISTEN_NONE); prefs.unregisterOnSharedPreferenceChangeListener(this); } /** * Description: Cell Information Tracking and database logging * * TODO: update this!! * * If the "tracking" option is enabled (as it is by default) then we are keeping * a record (tracking) of the device location "gpsd_lat/lon", the connection * signal strength (rx_signal) and data activity (?) and data connection state (?). * * The items included in these are stored in the "DBi_measure" table. * * DATA_ACTIVITY: * DATA_CONNECTION_STATE: * * * UI/function: Drawer: "Toggle Cell Tracking" * * Issues: * * Notes: TODO: We also need to listen and log for: * * [ ] LISTEN_CALL_STATE: * CALL_STATE_IDLE * CALL_STATE_OFFHOOK * CALL_STATE_RINGING * * [ ] LISTEN_SERVICE_STATE: * STATE_EMERGENCY_ONLY * STATE_IN_SERVICE * STATE_OUT_OF_SERVICE * STATE_POWER_OFF * * @param track Enable/Disable tracking */ public void setCellTracking(boolean track) { if (track) { tm.listen(cellSignalListener, PhoneStateListener.LISTEN_CELL_LOCATION | // gpsd_lat/lon ? PhoneStateListener.LISTEN_SIGNAL_STRENGTHS | // rx_signal PhoneStateListener.LISTEN_DATA_ACTIVITY | // No,In,Ou,IO,Do PhoneStateListener.LISTEN_DATA_CONNECTION_STATE | // Di,Ct,Cd,Su PhoneStateListener.LISTEN_CELL_INFO // !? (Need API 17) ); trackingCell = true; Helpers.msgShort(context, context.getString(R.string.tracking_cell_information)); } else { tm.listen(cellSignalListener, PhoneStateListener.LISTEN_NONE); device.cell.setLon(0.0); device.cell.setLat(0.0); device.setCellInfo("[0,0]|nn|nn|"); //default entries into "locationinfo"::Connection trackingCell = false; Helpers.msgShort(context, context.getString(R.string.stopped_tracking_cell_information)); } setNotification(); } /** * Description: This handles the settings/choices and default preferences, when changed. * From the default file: * preferences.xml * And saved in the file: * /data/data/com.SecUpwN.AIMSICD/shared_prefs/com.SecUpwN.AIMSICD_preferences.xml * * NOTE: - For more code transparency we have added TinyDB.java as a * wrapper to SharedPreferences usage. Please try to use this instead. * * @param sharedPreferences * @param key */ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { final String KEY_UI_ICONS = context.getString(R.string.pref_ui_icons_key); final String FEMTO_DETECTION = context.getString(R.string.pref_femto_detection_key); final String REFRESH = context.getString(R.string.pref_refresh_key); final String OCID_KEY = context.getString(R.string.pref_ocid_key); final String VIBRATE_ENABLE = context.getString(R.string.pref_notification_vibrate_enable); final String VIBRATE_MIN_LEVEL = context.getString(R.string.pref_notification_vibrate_min_level); if (key.equals(KEY_UI_ICONS)) { // Update Notification to display selected icon type setNotification(); } else if (key.equals(FEMTO_DETECTION)) { boolean trackFemtoPref = sharedPreferences.getBoolean(FEMTO_DETECTION, false); if (trackFemtoPref) { startTrackingFemto(); } else { stopTrackingFemto(); } } else if (key.equals(REFRESH)) { String refreshRate = sharedPreferences.getString(REFRESH, "1"); if (refreshRate.isEmpty()) { refreshRate = "1"; // Set default to: 1 second } int rate = Integer.parseInt(refreshRate); long t; switch (rate) { case 1: t = 15L; // Automatic refresh rate is 15 seconds break; default: t = (long) rate; // Default is 1 sec (from above) break; } REFRESH_RATE = TimeUnit.SECONDS.toMillis(t); } else if (key.equals(OCID_KEY)) { getOcidKey(); } else if (key.equals(VIBRATE_ENABLE)) { vibrateEnabled = sharedPreferences.getBoolean(VIBRATE_ENABLE, true); } else if (key.equals(VIBRATE_MIN_LEVEL)) { vibrateMinThreatLevel = Integer.valueOf(sharedPreferences.getString(VIBRATE_MIN_LEVEL, String.valueOf(Status.MEDIUM.ordinal()))); } } public void getOcidKey() { final String OCID_KEY = context.getString(R.string.pref_ocid_key); OCID_API_KEY = prefs.getString(OCID_KEY, BuildConfig.OPEN_CELLID_API_KEY); if (OCID_API_KEY == null) { OCID_API_KEY = "NA"; // avoid null api key } } /** * Description: Updates Neighboring Cell details * * TODO: add more details... * * */ public List<Cell> updateNeighboringCells() { List<Cell> neighboringCells = new ArrayList<>(); List<NeighboringCellInfo> neighboringCellInfo = tm.getNeighboringCellInfo(); if (neighboringCellInfo == null) { neighboringCellInfo = new ArrayList<>(); } Boolean nclp = tinydb.getBoolean("nc_list_present"); // NC list present? (default is false) //if nclp = true then check for neighboringCellInfo if (neighboringCellInfo != null && neighboringCellInfo.size() == 0 && nclp) { log.info("NeighboringCellInfo is empty: start polling..."); // Try to poll the neighboring cells for a few seconds neighboringCellBlockingQueue = new LinkedBlockingQueue<>(100); // TODO What is this ?? //LISTEN_CELL_INFO added in API 17 // TODO: See issue #555 (DeviceApi17.java is using API 18 CellInfoWcdma calls. if (Build.VERSION.SDK_INT > 17) { DeviceApi18.startListening(tm, phoneStatelistener); } else { tm.listen(phoneStatelistener, PhoneStateListener.LISTEN_CELL_LOCATION | PhoneStateListener.LISTEN_CELL_INFO | // API 17 PhoneStateListener.LISTEN_DATA_CONNECTION_STATE | PhoneStateListener.LISTEN_SERVICE_STATE | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS); } // TODO: Consider removing ?? for (int i = 0; i < 10 && neighboringCellInfo.size() == 0; i++) { try { log.debug("NeighboringCellInfo empty: trying " + i); NeighboringCellInfo info = neighboringCellBlockingQueue.poll(1, TimeUnit.SECONDS); if (info == null) { neighboringCellInfo = tm.getNeighboringCellInfo(); if (neighboringCellInfo != null) { if (neighboringCellInfo.size() > 0) { // Can we think of a better log message here? log.debug("NeighboringCellInfo found on " + i + " try. (time based)"); break; } else { continue; } } } List<NeighboringCellInfo> cellInfoList = new ArrayList<>(neighboringCellBlockingQueue.size() + 1); while (info != null) { cellInfoList.add(info); info = neighboringCellBlockingQueue.poll(1, TimeUnit.SECONDS); } neighboringCellInfo = cellInfoList; } catch (InterruptedException e) { // TODO: Add a more valuable message here! log.error("", e); } } } //log.debug(mTAG + ": neighboringCellInfo size: " + neighboringCellInfo.size()); // Add NC list to DBi_measure:nc_list for (NeighboringCellInfo neighborCell : neighboringCellInfo) { log.info("NeighboringCellInfo -" + " LAC:" + neighborCell.getLac() + " CID:" + neighborCell.getCid() + " PSC:" + neighborCell.getPsc() + " RSSI:" + neighborCell.getRssi()); final Cell cell = new Cell( neighborCell.getCid(), neighborCell.getLac(), neighborCell.getRssi(), neighborCell.getPsc(), neighborCell.getNetworkType(), false); neighboringCells.add(cell); } return neighboringCells; } /** * Description: This snippet sets a global variable (SharedPreference) to indicate * if Neighboring cells info CAN be obtained or has been obtained * previously. If it has been and suddenly there are none, we can * raise a flag of CID being suspicious. * * The logic is: * * IF NC has never been seen on device: * - NC list is NOT supported on this AOS/HW, so we do nothing. * IF NC has been seen before, * - NC list IS supported on this AOS/HW, so we set: * nc_list_present : "true" * IF NC list has been seen before AND current CID doesn't provide * one, we raise an alarm or flag. * * * Notes: a) Not sure where to place this test, but let's try it here.. * b) In TinyDB, the getBoolean() returns "false" by default, if empty. * * c) This will be called on every cell change (ref: issue #346) * d) *** https://github.com/CellularPrivacy/Android-IMSI-Catcher-Detector/issues/383 * * Issue: * [ ] We need a timer or "something" to reverse a positive detection once * we're out and away from the fake BTS cell. * [ ] We need to add this to EventLog * [ ] We need to add detection tickers etc... * [x] We need to use a global and persistent variable and not a system property * */ public void checkForNeighborCount(CellLocation location) { log.info("CheckForNeighborCount()"); Integer ncls = 0; // NC list size if (tm != null && tm.getNeighboringCellInfo() != null) { // See # 383 ncls = tm.getNeighboringCellInfo().size(); } Boolean nclp = tinydb.getBoolean("nc_list_present"); // NC list present? (default is false) if (ncls > 0) { log.debug("NeighboringCellInfo size: " + ncls); if (!nclp) { log.debug("Setting nc_list_present to: true"); tinydb.putBoolean("nc_list_present", true); } } else if (ncls == 0 && nclp) { // Detection 7a log.info("ALERT: No neighboring cells detected for CID: " + device.cell.getCellId()); vibrate(100, Status.MEDIUM); @Cleanup Realm realm = Realm.getDefaultInstance(); dbHelper.toEventLog(realm, 4, "No neighboring cells detected"); // (DF_id, DF_desc) } else { // Todo: remove cid string when working. log.debug("NC list not supported by AOS on this device. Nothing to do."); log.debug(": Setting nc_list_present to: false"); tinydb.putBoolean("nc_list_present", false); } } /** * I removed the timer that activated this code and now the code will be run when * the cell changes so it will detect faster rather than using a timer that might * miss an imsi catcher, also says cpu rather than refreshing every x seconds. * * original comments below from xLaMbChOpSx * * * Description: (From xLaMbChOpSx commit comment) * * Initial implementation for detection method 1 to compare the CID & LAC with the Cell * Information Table contents as an initial implementation for detection of a changed LAC, * once OCID issues (API key use etc) have been finalised this detection method can be * extended to include checking of external data. * * REMOVED: refresh timer info * * As I have no real way of testing this I require testing by other project members who * do have access to equipment or an environment where a changing LAC can be simulated * thus confirming the accuracy of this implementation. * * Presently this will only invoke the MEDIUM threat level through the notification and * does not fully implement the capturing and score based method as per the issue details * once further testing is complete the alert and tracking of information can be refined. * * See: * https://github.com/xLaMbChOpSx/Android-IMSI-Catcher-Detector/commit/43ae77e2a0cad10dfd50f92da5a998f9ece95b38 * https://github.com/SecUpwN/Android-IMSI-Catcher-Detector/issues/91#issuecomment-64391732 * * Short explanation: * * This is a polling mechanism for getting the LAC/CID and location * info for the currently connected cell. * * Variables: * FIXED: now updates on cell change rather than a timer * There is a "timer" here (REFRESH_RATE), what exactly is it timing? * "Every REFRESH_RATE seconds, get connected cell details." * * Issues: [ ] We shouldn't do any detection here! * [ ] We might wanna use a listener to do this? * Are there any reasons why not using a listener? * * ChangeLog: * 2015-03-03 E:V:A Changed getProp() to use TinyDB (SharedPreferences) * 2015-0x-xx banjaxbanjo Update: ??? (hey dude what did you do?) * */ public void compareLac(CellLocation location) { @Cleanup Realm realm = Realm.getDefaultInstance(); switch (device.getPhoneId()) { case TelephonyManager.PHONE_TYPE_NONE: case TelephonyManager.PHONE_TYPE_SIP: case TelephonyManager.PHONE_TYPE_GSM: GsmCellLocation gsmCellLocation = (GsmCellLocation) location; if (gsmCellLocation != null) { monitorCell.setLocationAreaCode(gsmCellLocation.getLac()); monitorCell.setCellId(gsmCellLocation.getCid()); // Check if LAC is ok boolean lacOK = dbHelper.checkLAC(realm, monitorCell); if (!lacOK) { changedLAC = true; dbHelper.toEventLog(realm, 1, "Changing LAC"); // Detection Logs are made in checkLAC() vibrate(100, Status.MEDIUM); } else { changedLAC = false; } if (tinydb.getBoolean("ocid_downloaded")) { if (!dbHelper.openCellExists(realm, monitorCell.getCellId())) { dbHelper.toEventLog(realm, 2, "CID not in Import realm"); log.info("ALERT: Connected to unknown CID not in Import realm: " + monitorCell.getCellId()); vibrate(100, Status.MEDIUM); cellIdNotInOpenDb = true; } else { cellIdNotInOpenDb = false; } } } break; case TelephonyManager.PHONE_TYPE_CDMA: CdmaCellLocation cdmaCellLocation = (CdmaCellLocation) location; if (cdmaCellLocation != null) { monitorCell.setLocationAreaCode(cdmaCellLocation.getNetworkId()); monitorCell.setCellId(cdmaCellLocation.getBaseStationId()); boolean lacOK = dbHelper.checkLAC(realm, monitorCell); if (!lacOK) { changedLAC = true; /*dbHelper.insertEventLog( MiscUtils.getCurrentTimeStamp(), monitorCell.getLAC(), monitorCell.getCid(), monitorCell.getPSC(),//This is giving weird values like 21478364... is this right? String.valueOf(monitorCell.getLat()), String.valueOf(monitorCell.getLon()), (int)monitorCell.getAccuracy(), 1, "Changing LAC" );*/ dbHelper.toEventLog(realm, 1, "Changing LAC"); } else { changedLAC = false; } } } setNotification(); } /** * Check device's current cell location's LAC against local database AND verify cell's CID * exists in the OCID database. * * @see #compareLac(CellLocation) */ public void compareLacAndOpenDb() { compareLac(tm.getCellLocation()); } // Where is this used? private void handlePhoneStateChange() { List<NeighboringCellInfo> neighboringCellInfo = tm.getNeighboringCellInfo(); if (neighboringCellInfo == null || neighboringCellInfo.size() == 0) { return; } log.info("NeighboringCellInfo empty - event based polling succeeded!"); tm.listen(phoneStatelistener, PhoneStateListener.LISTEN_NONE); if (neighboringCellInfo == null) { neighboringCellInfo = new ArrayList<>(); } neighboringCellBlockingQueue.addAll(neighboringCellInfo); } public void refreshDevice() { device.refreshDeviceInfo(tm, context); } /** * Description: Process User Preferences * This loads the default Settings/Preferences as set in: * preferences.xml * and: * /data/data/com.SecUpwN.AIMSICD/shared_prefs/com.SecUpwN.AIMSICD_preferences.xml * * TODO: Please add more info * */ private void loadPreferences() { // defaults are given by: getBoolean(key, default if not exist) boolean trackFemtoPref = prefs.getBoolean(context.getString(R.string.pref_femto_detection_key), false); boolean trackCellPref = prefs.getBoolean(context.getString(R.string.pref_enable_cell_key), true); boolean monitorCellPref = prefs.getBoolean(context.getString(R.string.pref_enable_cell_monitoring_key), true); CELL_TABLE_CLEANSED = prefs.getBoolean(context.getString(R.string.pref_cell_table_cleansed), false); String refreshRate = prefs.getString(context.getString(R.string.pref_refresh_key), "1"); this.vibrateEnabled = prefs.getBoolean(context.getString(R.string.pref_notification_vibrate_enable), true); this.vibrateMinThreatLevel = Integer.valueOf(prefs.getString(context.getString(R.string.pref_notification_vibrate_min_level), String.valueOf(Status.MEDIUM.ordinal()))); // Default to Automatic ("1") if (refreshRate.isEmpty()) { refreshRate = "1"; } int rate = Integer.parseInt(refreshRate); long t; if (rate == 1) { t = 15L; // Automatic refresh rate is 15 seconds } else { t = ((long) rate); // Default is 1 sec (from above) } REFRESH_RATE = TimeUnit.SECONDS.toMillis(t); getOcidKey(); if (trackFemtoPref) { startTrackingFemto(); } if (trackCellPref) { setCellTracking(true); } if (monitorCellPref) { setCellMonitoring(true); } } /** * Description: TODO: add more info * * This SEEM TO add entries to the "locationinfo" DB table in the ?? * * Issues: * * [ ] We see that "Connection" items are messed up. What is the purpose of these? * [ ] TODO: CDMA has to extract the MCC and MNC using something like: * * String mccMnc = phoneMgr.getNetworkOperator(); * String cdmaMcc = ""; * String cdmaMnc = ""; * if (mccMnc != null && mccMnc.length() >= 5) { * cdmaMcc = mccMnc.substring(0, 3); * cdmaMnc = mccMnc.substring(3, 5); } } * * ChangeLog: * * 2015-01-22 E:V:A Changed what appears to be a typo in the character * following getNetworkTypeName(), "|" to "]" * 2015-01-24 E:V:A FC WTF!? Changed back ^ to "|". (Where is this info parsed?) * */ private final PhoneStateListener cellSignalListener = new PhoneStateListener() { public void onCellLocationChanged(CellLocation location) { checkForNeighborCount(location); compareLac(location); refreshDevice(); device.setNetID(tm); device.getNetworkTypeName(); switch (device.getPhoneId()) { case TelephonyManager.PHONE_TYPE_NONE: case TelephonyManager.PHONE_TYPE_SIP: case TelephonyManager.PHONE_TYPE_GSM: GsmCellLocation gsmCellLocation = (GsmCellLocation) location; if (gsmCellLocation != null) { //TODO @EVA where are we sending this setCellInfo data? //TODO /*@EVA Is it a good idea to dump all cells to db because if we spot a known cell with different locationAreaCode then this will also be dump to db. */ device.setCellInfo( gsmCellLocation.toString() + // ?? device.getDataActivityTypeShort() + "|" + // No,In,Ou,IO,Do device.getDataStateShort() + "|" + // Di,Ct,Cd,Su device.getNetworkTypeName() + "|" // HSPA,LTE etc ); device.cell.setLocationAreaCode(gsmCellLocation.getLac()); // LAC device.cell.setCellId(gsmCellLocation.getCid()); // CID if (gsmCellLocation.getPsc() != -1) { device.cell.setPrimaryScramblingCode(gsmCellLocation.getPsc()); // PSC } /* Add cell if gps is not enabled when gps enabled lat lon will be updated by function below */ } break; case TelephonyManager.PHONE_TYPE_CDMA: CdmaCellLocation cdmaCellLocation = (CdmaCellLocation) location; if (cdmaCellLocation != null) { device.setCellInfo( cdmaCellLocation.toString() + // ?? device.getDataActivityTypeShort() + "|" + // No,In,Ou,IO,Do device.getDataStateShort() + "|" + // Di,Ct,Cd,Su device.getNetworkTypeName() + "|" // HSPA,LTE etc ); device.cell.setLocationAreaCode(cdmaCellLocation.getNetworkId()); // NID device.cell.setCellId(cdmaCellLocation.getBaseStationId()); // BID device.cell.setSid(cdmaCellLocation.getSystemId()); // SID device.cell.setMobileNetworkCode(cdmaCellLocation.getSystemId()); // MNC <== BUG!?? device.setNetworkName(tm.getNetworkOperatorName()); // ?? } } } /** * Description: TODO: add more info * * Issues: * * [ ] Getting and comparing signal strengths between different RATs can be very * tricky, since they all return different ranges of values. AOS doesn't * specify very clearly what exactly is returned, even though people have * a good idea, by trial and error. * * See note in : SignalStrengthTracker.java */ public void onSignalStrengthsChanged(SignalStrength signalStrength) { // Update Signal Strength if (signalStrength.isGsm()) { int dbm; if (signalStrength.getGsmSignalStrength() <= 2 || signalStrength.getGsmSignalStrength() == NeighboringCellInfo.UNKNOWN_RSSI) { // Unknown signal strength, get it another way String[] bits = signalStrength.toString().split(" "); dbm = Integer.parseInt(bits[9]); } else { dbm = signalStrength.getGsmSignalStrength(); } device.setSignalDbm(dbm); } else { int evdoDbm = signalStrength.getEvdoDbm(); int cdmaDbm = signalStrength.getCdmaDbm(); // Use lowest signal to be conservative device.setSignalDbm((cdmaDbm < evdoDbm) ? cdmaDbm : evdoDbm); } // Send it to signal tracker signalStrengthTracker.registerSignalStrength(device.cell.getCellId(), device.getSignalDBm()); //signalStrengthTracker.isMysterious(device.cell.getCid(), device.getSignalDBm()); } // In DB: No,In,Ou,IO,Do public void onDataActivity(int direction) { switch (direction) { case TelephonyManager.DATA_ACTIVITY_NONE: device.setDataActivityTypeShort("No"); device.setDataActivityType("None"); break; case TelephonyManager.DATA_ACTIVITY_IN: device.setDataActivityTypeShort("In"); device.setDataActivityType("In"); break; case TelephonyManager.DATA_ACTIVITY_OUT: device.setDataActivityTypeShort("Ou"); device.setDataActivityType("Out"); break; case TelephonyManager.DATA_ACTIVITY_INOUT: device.setDataActivityTypeShort("IO"); device.setDataActivityType("In-Out"); break; case TelephonyManager.DATA_ACTIVITY_DORMANT: device.setDataActivityTypeShort("Do"); device.setDataActivityType("Dormant"); break; } } // In DB: Di,Ct,Cd,Su public void onDataConnectionStateChanged(int state) { switch (state) { case TelephonyManager.DATA_DISCONNECTED: device.setDataState("Disconnected"); device.setDataStateShort("Di"); break; case TelephonyManager.DATA_CONNECTING: device.setDataState("Connecting"); device.setDataStateShort("Ct"); break; case TelephonyManager.DATA_CONNECTED: device.setDataState("Connected"); device.setDataStateShort("Cd"); break; case TelephonyManager.DATA_SUSPENDED: device.setDataState("Suspended"); device.setDataStateShort("Su"); break; } } }; /** * Add entries to the {@link com.secupwn.aimsicd.data.model.Measure Measure} realm */ public void onLocationChanged(Location loc) { // TODO: See issue #555 (DeviceApi17.java is using API 18 CellInfoWcdma calls. if (Build.VERSION.SDK_INT > 17) { DeviceApi18.loadCellInfo(tm, device); } if (!device.cell.isValid()) { CellLocation cellLocation = tm.getCellLocation(); if (cellLocation != null) { switch (device.getPhoneId()) { case TelephonyManager.PHONE_TYPE_NONE: case TelephonyManager.PHONE_TYPE_SIP: case TelephonyManager.PHONE_TYPE_GSM: GsmCellLocation gsmCellLocation = (GsmCellLocation) cellLocation; device.cell.setCellId(gsmCellLocation.getCid()); // CID device.cell.setLocationAreaCode(gsmCellLocation.getLac()); // LAC device.cell.setPrimaryScramblingCode(gsmCellLocation.getPsc()); // PSC break; case TelephonyManager.PHONE_TYPE_CDMA: CdmaCellLocation cdmaCellLocation = (CdmaCellLocation) cellLocation; device.cell.setCellId(cdmaCellLocation.getBaseStationId()); // BSID ?? device.cell.setLocationAreaCode(cdmaCellLocation.getNetworkId()); // NID device.cell.setSid(cdmaCellLocation.getSystemId()); // SID device.cell.setMobileNetworkCode(cdmaCellLocation.getSystemId()); // MNC <== BUG!?? break; } } } if (loc != null && (Double.doubleToRawLongBits(loc.getLatitude()) != 0 && Double.doubleToRawLongBits(loc.getLongitude()) != 0)) { device.cell.setLon(loc.getLongitude()); // gpsd_lon device.cell.setLat(loc.getLatitude()); // gpsd_lat device.cell.setSpeed(loc.getSpeed()); // speed // TODO: Remove, we're not using it! device.cell.setAccuracy(loc.getAccuracy()); // gpsd_accu device.cell.setBearing(loc.getBearing()); // -- [deg]?? // TODO: Remove, we're not using it! device.setLastLocation(loc); // // Store last known location in preference SharedPreferences.Editor prefsEditor; prefsEditor = prefs.edit(); prefsEditor.putString(context.getString(R.string.data_last_lat_lon), String.valueOf(loc.getLatitude()) + ":" + String.valueOf(loc.getLongitude())); prefsEditor.apply(); // This only logs a BTS if we have GPS lock // TODO: Is correct behaviour? We should consider logging all cells, even without GPS. if (trackingCell) { // This also checks that the locationAreaCode are cid are not in DB before inserting @Cleanup Realm realm = Realm.getDefaultInstance(); dbHelper.insertBTS(realm, device.cell); } } } /** * Cancel and remove the persistent notification */ public void cancelNotification() { NotificationManagerCompat.from(context).cancel(NOTIFICATION_ID); } /** * Description: Set or update the Detection/Status Notification * TODO: Need to add status HIGH (Orange) and SKULL (Black) * * Issues: * See: https://github.com/SecUpwN/Android-IMSI-Catcher-Detector/wiki/Status-Icons * and: https://github.com/SecUpwN/Android-IMSI-Catcher-Detector/issues/11#issuecomment-44670204 * * [ ] We need to standardize the "contentText" and "tickerText" format * * [ ] From #91: https://github.com/SecUpwN/Android-IMSI-Catcher-Detector/issues/91 * * Problem: * Having multiple notifications will cause an issue with * notifications themselves AND tickerText. It seems that the * most recent notification raised would overwrite any previous, * notification or tickerText. This results in loss of information * for any notification before the last one. * * Possible Solution: * Perhaps arranging a queue implementation to deal with text * being passed into tickerText only when any previous text has * been entirely displayed. * * Dependencies: Status.java, CellTracker.java, Icon.java ( + others?) * */ void setNotification() { String tickerText; String contentText = "Phone Type " + device.getPhoneType(); if (femtoDetected || typeZeroSmsDetected) { getApplication().setCurrentStatus(Status.DANGER, vibrateEnabled, vibrateMinThreatLevel); } else if (changedLAC) { getApplication().setCurrentStatus(Status.MEDIUM, vibrateEnabled, vibrateMinThreatLevel); contentText = context.getString(R.string.hostile_service_area_changing_lac_detected); } else if (cellIdNotInOpenDb) { getApplication().setCurrentStatus(Status.MEDIUM, vibrateEnabled, vibrateMinThreatLevel); contentText = context.getString(R.string.cell_id_doesnt_exist_in_db); } else if (trackingFemtocell || trackingCell || monitoringCell) { getApplication().setCurrentStatus(Status.OK, vibrateEnabled, vibrateMinThreatLevel); if (trackingFemtocell) { contentText = context.getString(R.string.femtocell_detection_active); } else if (trackingCell) { contentText = context.getString(R.string.cell_tracking_active); } if (monitoringCell) { contentText = context.getString(R.string.cell_monitoring_active); } else { getApplication().setCurrentStatus(Status.IDLE, vibrateEnabled, vibrateMinThreatLevel); } } else { getApplication().setCurrentStatus(Status.IDLE, vibrateEnabled, vibrateMinThreatLevel); } Status status = getApplication().getStatus(); switch (status) { case IDLE: // GRAY contentText = context.getString(R.string.phone_type) + device.getPhoneType(); tickerText = context.getResources().getString(R.string.app_name_short) + " " + context.getString(R.string.status_idle_description); break; case OK: // GREEN tickerText = context.getResources().getString(R.string.app_name_short) + " " + context.getString(R.string.status_ok_description); break; case MEDIUM: // YELLOW // Initialize tickerText as the app name string // See multiple detection comments above. tickerText = context.getResources().getString(R.string.app_name_short); if (changedLAC) { //Append changing LAC text contentText = context.getString(R.string.hostile_service_area_changing_lac_detected); tickerText += " - " + contentText; // See #264 and ask He3556 //} else if (mNoNCList) { // tickerText += " - BTS doesn't provide any neighbors!"; // contentText = "CID: " + cellid + " is not providing a neighboring cell list!"; } else if (cellIdNotInOpenDb) { //Append Cell ID not existing in external db text contentText = context.getString(R.string.cell_id_doesnt_exist_in_db); tickerText += " - " + contentText; } break; case DANGER: // RED tickerText = context.getResources().getString(R.string.app_name_short) + " - " + context.getString(R.string.alert_threat_detected); // Hmm, this is vague! if (femtoDetected) { contentText = context.getString(R.string.alert_femtocell_connection_detected); } else if (typeZeroSmsDetected) { contentText = context.getString(R.string.alert_silent_sms_detected); } break; default: tickerText = context.getResources().getString(R.string.main_app_name); break; } // TODO: Explanation (see above) Intent notificationIntent = new Intent(context, MainActivity.class); notificationIntent.putExtra("silent_sms", typeZeroSmsDetected); notificationIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_FROM_BACKGROUND); PendingIntent contentIntent = PendingIntent.getActivity( context, NOTIFICATION_ID, notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT); String iconType = prefs.getString(context.getString(R.string.pref_ui_icons_key), "SENSE").toUpperCase(); int iconResId = Icon.getIcon(Icon.Type.valueOf(iconType), status); Bitmap largeIcon = BitmapFactory.decodeResource(context.getResources(), iconResId); int color = context.getResources().getColor(status.getColor()); Notification notification = new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.tower48) .setColor(color) .setLargeIcon(largeIcon) .setTicker(tickerText) .setContentTitle(context.getString(R.string.status) + " " + context.getString(status.getName())) .setContentInfo(context.getResources().getString(R.string.app_name_short)) .setContentText(contentText) .setOngoing(true) .setAutoCancel(false) .setContentIntent(contentIntent) .build(); NotificationManagerCompat .from(context) .notify(NOTIFICATION_ID, notification); } private AndroidIMSICatcherDetector getApplication() { return AndroidIMSICatcherDetector.getInstance(); } /** * Vibrator helper method, will check current preferences (vibrator enabled, min threat level to vibrate) * and act appropriately * */ private void vibrate(int msec, Status threatLevel) { if (vibrateEnabled && (threatLevel == null || threatLevel.ordinal() >= vibrateMinThreatLevel)) { Vibrator v = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); v.vibrate(msec); } } //================================================================================================= // TODO: Consider REMOVAL! See issues: #6, #457, #489 // TODO: Summary: We can detect femtocells by other means, using network data that we already have! // The below code section was copied and modified with permission from // Femtocatcher at: https://github.com/iSECPartners/femtocatcher // // Copyright (C) 2013 iSEC Partners //================================================================================================= /** * Start FemtoCell detection tracking (For CDMA Devices ONLY!) */ public void startTrackingFemto() { /* Check if it is a CDMA phone */ if (device.getPhoneId() != TelephonyManager.PHONE_TYPE_CDMA) { Helpers.msgShort(context, context.getString(R.string.femtocell_only_on_cdma_devices)); return; } trackingFemtocell = true; mPhoneStateListener = new PhoneStateListener() { public void onServiceStateChanged(ServiceState s) { log.debug(context.getString(R.string.service_state_changed)); getServiceStateInfo(s); } }; tm.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CELL_LOCATION); tm.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE); setNotification(); } /** * Stop FemtoCell detection tracking (For CDMA Devices ONLY!) */ public void stopTrackingFemto() { if (mPhoneStateListener != null) { tm.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); trackingFemtocell = false; setNotification(); log.verbose(context.getString(R.string.stopped_tracking_femtocell)); } } private void getServiceStateInfo(ServiceState s) { if (s != null) { if (IsConnectedToCdmaFemto(s)) { Helpers.msgShort(context, context.getString(R.string.alert_femtocell_tracking_detected)); femtoDetected = true; setNotification(); //toggleRadio(); } else { femtoDetected = false; setNotification(); } } } private boolean IsConnectedToCdmaFemto(ServiceState s) { if (s == null) { return false; } /* Get International Roaming indicator * if indicator is not 0 return false */ /* Get the radio technology */ int networkType = device.cell.getNetType(); /* Check if it is EvDo network */ boolean evDoNetwork = isEvDoNetwork(networkType); /* If it is not an evDo network check the network ID range. * If it is connected to Femtocell, the NID should be between [0xfa, 0xff) */ if (!evDoNetwork) { /* get network ID */ if (tm != null) { CdmaCellLocation c = (CdmaCellLocation) tm.getCellLocation(); if (c != null) { int networkID = c.getNetworkId(); int FEMTO_NID_MAX = 0xff; int FEMTO_NID_MIN = 0xfa; return !((networkID < FEMTO_NID_MIN) || (networkID >= FEMTO_NID_MAX)); } else { log.verbose("Cell location info is null."); return false; } } else { log.verbose("Telephony Manager is null."); return false; } } else { /* if it is an evDo network */ /* get network ID */ if (tm != null) { CdmaCellLocation c = (CdmaCellLocation) tm.getCellLocation(); if (c != null) { int networkID = c.getNetworkId(); int FEMTO_NID_MAX = 0xff; int FEMTO_NID_MIN = 0xfa; return !((networkID < FEMTO_NID_MIN) || (networkID >= FEMTO_NID_MAX)); } else { log.verbose("Cell location info is null."); return false; } } else { log.verbose("Telephony Manager is null."); return false; } } } /** * Confirmation of connection to an EVDO Network * * @return EVDO network connection returns TRUE */ private boolean isEvDoNetwork(int networkType) { return (networkType == TelephonyManager.NETWORK_TYPE_EVDO_0) || (networkType == TelephonyManager.NETWORK_TYPE_EVDO_A) || (networkType == TelephonyManager.NETWORK_TYPE_EVDO_B) || (networkType == TelephonyManager.NETWORK_TYPE_EHRPD); } //================================================================================================= // END Femtocatcher code //================================================================================================= final PhoneStateListener phoneStatelistener = new PhoneStateListener() { private void handle() { handlePhoneStateChange(); } @Override public void onServiceStateChanged(ServiceState serviceState) { handle(); } @Override public void onDataConnectionStateChanged(int state) { handle(); } @Override public void onDataConnectionStateChanged(int state, int networkType) { handle(); } @Override public void onSignalStrengthsChanged(SignalStrength signalStrength) { handle(); } @Override public void onCellInfoChanged(List<CellInfo> cellInfo) { handle(); } @Override public void onCellLocationChanged(CellLocation location) { handle(); } }; }