/* Android IMSI-Catcher Detector | (c) AIMSICD Privacy Project * ----------------------------------------------------------- * LICENSE: http://git.io/vki47 | TERMS: http://git.io/vki4o * ----------------------------------------------------------- */ package zz.aimsicd.lite.smsdetection; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; import android.support.annotation.StringRes; import android.support.v7.app.AlertDialog; import android.util.Log; import android.view.WindowManager; import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import zz.aimsicd.lite.R; import zz.aimsicd.lite.adapters.AIMSICDDbAdapter; import zz.aimsicd.lite.service.AimsicdService; import zz.aimsicd.lite.utils.MiscUtils; /** * Description: Detects mysterious SMS by scraping Logcat entries. * * * NOTES: For this to work better Samsung users might have to set their Debug Level to High * in SysDump menu *#9900# or *#*#9900#*#* * * This is by no means a complete detection method but gives us something to work off. * * For latest list of working phones/models, please see: * https://github.com/SecUpwN/Android-IMSI-Catcher-Detector/issues/532 * * PHONE:Samsung S5 MODEL:SM-G900F ANDROID_VER:4.4.2 TYPE0:YES MWI:YES * PHONE:Samsung S4-min MODEL:GT-I9195 ANDROID_VER:4.2.2 TYPE0:YES MWI:YES * PHONE:Sony Xperia J MODEL:ST260i ANDROID_VER:4.1.2 TYPE0:NO MWI:YES * * To Use: * * SmsDetector smsDetector = new SmsDetector(context); * * smsDetector.startSmsDetection(); * smsDetector.stopSmsDetection(); * * * TODO: * [ ] Add more mTAG to the detection Log items * * @author Paul Kinsella @banjaxbanjo */ public final class SmsDetector extends Thread { public static final String TAG = "AICDL"; public static final String mTAG = "SmsDetector"; private AimsicdService mAIMSICDService; private boolean mBound; private AIMSICDDbAdapter mDbAdapter; private Context mContext; private final String[] LOADED_DETECTION_STRINGS; private static final int TYPE0 = 1, MWI = 2, WAP = 3; // TODO: replace this with retrieval from AIMSICDDbAdapter private static final int LOGCAT_BUFFER_MAX_SIZE = 100; /** * To correctly detect sms data and phone numbers on wap, we need at least * 10 lines after line which indicates wap communication */ private static final int LOGCAT_WAP_EXTRA_LINES = 10; private static boolean isRunning = false; public SmsDetector(Context context) { mContext = context; mDbAdapter = new AIMSICDDbAdapter(context); List<AdvanceUserItems> silent_string = mDbAdapter.getDetectionStrings(); LOADED_DETECTION_STRINGS = new String[silent_string.size()]; for (int x = 0; x < silent_string.size(); x++) { LOADED_DETECTION_STRINGS[x] = silent_string.get(x).getDetection_string() + "#" + silent_string.get(x).getDetection_type(); } } public static boolean getSmsDetectionState() { return isRunning; } public static void setSmsDetectionState(boolean isRunning) { SmsDetector.isRunning = isRunning; } public void startPopUpInfo(SmsType smsType) { MiscUtils.showNotification( mContext, mContext.getString(smsType.getAlert()), mContext.getString(R.string.app_name_short) + " - " + mContext.getString(smsType.getTitle()), R.drawable.sense_danger, true); AlertDialog alertDialog = new AlertDialog.Builder(mContext) .setTitle(smsType.getTitle()) .setMessage(smsType.getMessage()) .setIcon(R.drawable.sense_danger) .create(); alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); alertDialog.show(); } public void startSmsDetection() { Intent intent = new Intent(mContext, AimsicdService.class); mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); start(); Log.i(TAG, mTAG + "SMS detection started"); } public void stopSmsDetection() { setSmsDetectionState(false); // Unbind from the service if (mBound) { mContext.unbindService(mConnection); mBound = false; } Log.i(TAG, mTAG + "SMS detection stopped"); } @Override public void run() { setSmsDetectionState(true); BufferedReader mLogcatReader; try { Thread.sleep(500); String MODE = "logcat -v time -b radio -b main\n"; Runtime r = Runtime.getRuntime(); Process process = r.exec("su"); DataOutputStream dos = new DataOutputStream(process.getOutputStream()); dos.writeBytes(MODE); dos.flush(); dos.close(); mLogcatReader = new BufferedReader(new InputStreamReader(process.getInputStream())); } catch (InterruptedException | IOException e) { Log.e(TAG, mTAG + "Exception while initializing LogCat (time, radio, main) reader", e); return; } String logcatLine; List<String> logcatLines = new ArrayList<>(); while (getSmsDetectionState()) { try { logcatLine = mLogcatReader.readLine(); if (logcatLines.size() <= LOGCAT_BUFFER_MAX_SIZE || logcatLine != null) { logcatLines.add(logcatLine); } else if (logcatLines.size() == 0) { /** * Sleep only when there is no more input, not after going through buffer * to not unnecessary slow down the process * */ Thread.sleep(1000); } else { /** * In moment, where there are no data * we check the current buffer and clear it * */ String[] outLines = new String[logcatLines.size()]; logcatLines.toArray(outLines); for (int counter = 0; counter < logcatLines.size(); counter++) { String bufferedLine = logcatLines.get(counter); switch (checkForSms(bufferedLine)) { case TYPE0: parseTypeZeroSms(outLines, MiscUtils.logcatTimeStampParser(bufferedLine)); break; case MWI: parseMwiSms(outLines, MiscUtils.logcatTimeStampParser(bufferedLine)); break; case WAP: int remainingLinesInBuffer = logcatLines.size() - counter - LOGCAT_WAP_EXTRA_LINES; if (remainingLinesInBuffer < 0) { /** * we need to go forward a few more lines to get data * and store it in post buffer array * */ String[] wapPostLines = new String[Math.abs(remainingLinesInBuffer)]; String extraLine; for (int x = 0; x < Math.abs(remainingLinesInBuffer); x++) { extraLine = mLogcatReader.readLine(); if (extraLine != null) { wapPostLines[x] = extraLine; } } /** * We'll add the extra lines to logcat buffer, so we don't miss anything * on detection cycle continue * */ int insertCounter = logcatLines.size(); for (String postLine : wapPostLines) { logcatLines.add(counter + insertCounter, postLine); insertCounter++; } } /** * Will readout from LogcatBuffer remaining lines, or next LOGCAT_WAP_EXTRA_LINES lines * depending on how many are available * */ int availableLines = Math.min(logcatLines.size() - counter - LOGCAT_WAP_EXTRA_LINES, LOGCAT_WAP_EXTRA_LINES); String[] nextAvailableLines = new String[availableLines]; for (int nextLine = 0; nextLine < availableLines; nextLine++) { nextAvailableLines[nextLine] = logcatLines.get(counter + nextLine); } parseWapPushSms(outLines, nextAvailableLines, MiscUtils.logcatTimeStampParser(bufferedLine)); break; } counter++; } logcatLines.clear(); } } catch (IOException e) { Log.e(TAG, mTAG + "IO Exception", e); } catch (InterruptedException e) { Log.e(TAG, mTAG + "Interrupted Exception", e); } } try { mLogcatReader.close(); } catch (IOException ee) { Log.e(TAG, mTAG + "IOE Error closing BufferedReader", ee); } } private int checkForSms(String line) { //0 - null 1 = TYPE0, 2 = MWI, 3 = WAPPUSH for (String LOADED_DETECTION_STRING : LOADED_DETECTION_STRINGS) { //looping through detection strings to see does logcat line match // memory optimized and precaution for LOADED_DETECTION_STRING being not filled String[] splitDetectionString = LOADED_DETECTION_STRING == null ? null : LOADED_DETECTION_STRING.split("#"); if (splitDetectionString == null || splitDetectionString.length < 2 || splitDetectionString[0] == null || splitDetectionString[1] == null) { Log.d(TAG, mTAG + "Broken detection string: " + LOADED_DETECTION_STRING); // skip broken detection string continue; } if (line.contains(splitDetectionString[0])) { if ("TYPE0".equalsIgnoreCase(splitDetectionString[1])) { Log.i(TAG, mTAG + "TYPE0 detected"); return TYPE0; } else if ("MWI".equalsIgnoreCase(splitDetectionString[1])) { Log.i(TAG, mTAG + "MWI detected"); return MWI; } else if ("WAPPUSH".equalsIgnoreCase(splitDetectionString[1])) { Log.i(TAG, mTAG + "WAPPUSH detected"); return WAP; } } // This is currently unused, but keeping as an example of possible data contents // else if (line.contains("BroadcastReceiver action: android.provider.Telephony.SMS_RECEIVED")) { // Log.i(TAG, mTAG + "SMS found"); // return 0; // } } return 0; } private void parseTypeZeroSms(String[] bufferLines, String logcat_timestamp) { CapturedSmsData capturedSms = new CapturedSmsData(); String smsText = findSmsData(bufferLines, null); String num = findSmsNumber(bufferLines, null); capturedSms.setSenderNumber(num == null ? "null" : num); capturedSms.setSenderMsg(smsText == null ? "null" : num); capturedSms.setSmsTimestamp(logcat_timestamp); capturedSms.setSmsType("TYPE0"); capturedSms.setCurrent_lac(mAIMSICDService.getCellTracker().getMonitorCell().getLAC()); capturedSms.setCurrent_cid(mAIMSICDService.getCellTracker().getMonitorCell().getCID()); capturedSms.setCurrent_nettype(mAIMSICDService.getCell().getRAT()); int isRoaming = 0; if ("true".equals(mAIMSICDService.getCellTracker().getDevice().isRoaming())) { isRoaming = 1; } capturedSms.setCurrent_roam_status(isRoaming); capturedSms.setCurrent_gps_lat(mAIMSICDService.lastKnownLocation().getLatitudeInDegrees()); capturedSms.setCurrent_gps_lon(mAIMSICDService.lastKnownLocation().getLongitudeInDegrees()); // Only alert if the timestamp is not in the data base if (!mDbAdapter.isTimeStampInDB(logcat_timestamp)) { mDbAdapter.storeCapturedSms(capturedSms); mDbAdapter.toEventLog(3, "Detected Type-0 SMS"); startPopUpInfo(SmsType.SILENT); } else { Log.d(TAG, mTAG + "Detected Sms already logged"); } } private void parseMwiSms(String[] logcatLines, String logcat_timestamp) { CapturedSmsData capturedSms = new CapturedSmsData(); String smsText = findSmsData(logcatLines, null); String num = findSmsNumber(logcatLines, null); capturedSms.setSenderNumber(num == null ? "null" : num); capturedSms.setSenderMsg(smsText == null ? "null" : smsText); capturedSms.setSmsTimestamp(logcat_timestamp); capturedSms.setSmsType("MWI"); capturedSms.setCurrent_lac(mAIMSICDService.getCellTracker().getMonitorCell().getLAC()); capturedSms.setCurrent_cid(mAIMSICDService.getCellTracker().getMonitorCell().getCID()); capturedSms.setCurrent_nettype(mAIMSICDService.getCell().getRAT()); int isRoaming = 0; if ("true".equals(mAIMSICDService.getCellTracker().getDevice().isRoaming())) { isRoaming = 1; } capturedSms.setCurrent_roam_status(isRoaming); capturedSms.setCurrent_gps_lat(mAIMSICDService.lastKnownLocation().getLatitudeInDegrees()); capturedSms.setCurrent_gps_lon(mAIMSICDService.lastKnownLocation().getLongitudeInDegrees()); //only alert if timestamp is not in the data base if (!mDbAdapter.isTimeStampInDB(logcat_timestamp)) { mDbAdapter.storeCapturedSms(capturedSms); mDbAdapter.toEventLog(4, "Detected MWI SMS"); startPopUpInfo(SmsType.MWI); } else { Log.d(TAG, mTAG + " Detected Sms already logged"); } } private void parseWapPushSms(String[] logcatLines, String[] postWapMessageLines, String logcat_timestamp) { CapturedSmsData capturedSms = new CapturedSmsData(); String smsText = findSmsData(logcatLines, postWapMessageLines); String num = findSmsNumber(logcatLines, postWapMessageLines); capturedSms.setSenderNumber(num == null ? "null" : num); capturedSms.setSenderMsg(smsText == null ? "null" : smsText); capturedSms.setSmsTimestamp(logcat_timestamp); capturedSms.setSmsType("WAPPUSH"); capturedSms.setCurrent_lac(mAIMSICDService.getCellTracker().getMonitorCell().getLAC()); capturedSms.setCurrent_cid(mAIMSICDService.getCellTracker().getMonitorCell().getCID()); capturedSms.setCurrent_nettype(mAIMSICDService.getCell().getRAT()); int isRoaming = 0; if ("true".equals(mAIMSICDService.getCellTracker().getDevice().isRoaming())) { isRoaming = 1; } capturedSms.setCurrent_roam_status(isRoaming); capturedSms.setCurrent_gps_lat(mAIMSICDService.lastKnownLocation().getLatitudeInDegrees()); capturedSms.setCurrent_gps_lon(mAIMSICDService.lastKnownLocation().getLongitudeInDegrees()); //only alert if timestamp is not in the data base if (!mDbAdapter.isTimeStampInDB(logcat_timestamp)) { mDbAdapter.storeCapturedSms(capturedSms); mDbAdapter.toEventLog(6, "Detected WAPPUSH SMS"); startPopUpInfo(SmsType.WAP_PUSH); } else { Log.d(TAG, mTAG + "Detected SMS already logged"); } } private String findSmsData(String[] preBuffer, String[] postBuffer) { //check pre buffer for number and sms msg if (preBuffer != null) { for (String preBufferLine : preBuffer) { if (preBufferLine != null) { if (preBufferLine.contains("SMS message body (raw):") && preBufferLine.contains("'")) { preBufferLine = preBufferLine.substring(preBufferLine.indexOf("'") + 1, preBufferLine.length() - 1); return preBufferLine; } } } //check post buffer for number and sms msg if (postBuffer != null) { for (int x = 0; x < postBuffer.length; x++) { if (postBuffer[x] != null) { String testLine = preBuffer[x]; if (testLine.contains("SMS message body (raw):") && testLine.contains("'")) { testLine = testLine.substring(testLine.indexOf("'") + 1, testLine.length() - 1); return testLine; } } } } } return null; } private String findSmsNumber(String[] preBuffer, String[] postBuffer) { //check pre buffer for number and sms msg if (preBuffer != null) { for (String preBufferLine : preBuffer) { if (preBufferLine != null) { if (preBufferLine.contains("SMS originating address:") && preBufferLine.contains("+")) { return preBufferLine.substring(preBufferLine.indexOf("+")); } else if (preBufferLine.contains("OrigAddr")) { preBufferLine = preBufferLine.substring(preBufferLine.indexOf("OrigAddr")).replace("OrigAddr", "").trim(); return preBufferLine; } } } } //check post buffer for number and sms msg if (postBuffer != null) { for (String postBufferLine : postBuffer) { if (postBufferLine != null) { if (postBufferLine.contains("SMS originating address:") && postBufferLine.contains("+")) { return postBufferLine.substring(postBufferLine.indexOf("+")); } else if (postBufferLine.contains("OrigAddr")) { postBufferLine = postBufferLine.substring(postBufferLine.indexOf("OrigAddr")).replace("OrigAddr", "").trim(); return postBufferLine; } } } } return null; } private final ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mAIMSICDService = ((AimsicdService.AimscidBinder) service).getService(); mBound = true; } @Override public void onServiceDisconnected(ComponentName arg0) { Log.i(TAG, mTAG + "Disconnected SMS Detection Service"); mBound = false; } }; public enum SmsType { SILENT( R.string.alert_silent_sms_detected, R.string.typezero_header, R.string.typezero_data ), MWI( R.string.alert_mwi_detected, R.string.typemwi_header, R.string.typemwi_data ), WAP_PUSH( R.string.alert_silent_wap_sms_detected, R.string.typewap_header, R.string.typewap_data ); @StringRes private int alert; @StringRes private int title; @StringRes private int message; SmsType(@StringRes int alert, @StringRes int title, @StringRes int message) { this.alert = alert; this.title = title; this.message = message; } @StringRes public int getAlert() { return alert; } @StringRes public int getTitle() { return title; } @StringRes public int getMessage() { return message; } } }