Pull to refresh

Пишем простейший сервис от кражи Android-коммуникатора

Reading time8 min
Views22K
Здравствуйте. Некоторое время назад я начал изучение программирования под Android. Начитавшись различной тематической литературы, как это обычно бывает, решил опробовать свои силы на этом поприще. Идея написания именно этого сервиса возникла еще в 2009 году, когда реклама на «РУ.ТВ» пестрила фразами — «Узнай местоположение абонента», «Перехват СМС» и т.п. Не нужно иметь ученую степень чтобы понять, что все это обычный «развод на деньги». К тому же, внимательный зритель обратит внимание на слово «игра», якобы невзначай оброненное в тексте. Смекнув, что подобного рода услуга возможна, я решил попробовать написать программу, которая будет перехватывать входящие/исходящие СМС, список звонков и определять местоположение. Но позиционировать ее как СМС-перехватчик, в рамках действующего законодательства, было, мягко говоря, незаконно. В связи с чем идея была заброшена, и я переключился на иные проекты до нынешних времен.

Причиной по которой я вновь взялся за этот проект была кража злоумышленниками этим летом моего новенького HTC Desire Z под управлением Android. Разумеется, моя SIM-карта была выброшена и вместе с ней исчезла последняя зацепка и возможность найти мой телефон. Вот тогда-то я и пожалел что не был внедрен в жизнь ранее задуманный проект и вмиг созрела идея, какую именно пользу от него можно извлечь в рамках закона.

Итак, все по порядку. Для реализации задачи был выбран язык программирования Java. Я не буду вдаваться в подробности причины выбора языка, но попробую максимально осветить специфические моменты в программировании под Android. В данной статье я рассмотрю алгоритмы работы с входящими и исходящими СМС.

Итак, работа системы делится на клиентскую (ту, которая устанавливается на защищаемый телефон) часть и серверную (некий интернет-сервис на который передаются данные со всех телефонов, на которые установлена программа слежения). Давайте рассмотрим что должна делать клиентская часть:

Так как программа должна вести постоянное слежение на наличие входящих или исходящих СМС, она должна автоматически запускаться при перезагрузке телефона и оставаться относительно невидимой для пользователя коммуникатора. Задачу включения функций отслеживания после перезапуска способен решить ресивер, объявленный в манифесте:

<receiver android:name=".onBootReceiver" android:enabled="true" android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>



Он запускает две службы, работающие в фоновом режиме — AlarmedService и GPS:


public class onBootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent)
{
if ("android.intent.action.BOOT_COMPLETED".equals(intent.getAction()))
{
//Запускаем службу Alarm
Intent serviceLauncher = new Intent(context, AlarmedService.class);
context.startService(serviceLauncher);
//Запускаем службу GPS
if (Consts.STARTUP_GPS_SERVICE) context.startService(new Intent(context, GPS.class));
}
}
}


Так как в данной статье я планирую рассмотреть лишь работу перехватчика СМС, поясню назначение первой службы Alarmed Service.

private void startService()
{
//пересылка неотправленных смс
Intent intent = new Intent(this, ResendData.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, REQUEST_CODE, intent, 0);
alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + FIRST_RUN, INTERVAL, pendingIntent);
//перехват отправленных смс
Intent intent2 = new Intent(this, OutgoingSMS.class);
PendingIntent pendingIntent2 = PendingIntent.getBroadcast(this, REQUEST_CODE, intent2, 0);
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + Consts.FIRST_RUN_500ms , Consts.INTERVAL_1sec , pendingIntent2);
}


Это служба, вызываемая AlarmManagerом, которая с некоторой периодичностью повторяет запуск функций, а именно, повторную отправку ранее не отправленных на сервер СМС (по причине отсутствия Интернет, например) и проверку на наличие новых исходящих СМС. Забегая вперед, поясню, что обработка входящих СМС реализована несколько иначе, а точнее, через приемник широковещательных намерений BroadcastReciever и работает значительно эффективнее, чем в случае с исходящими. И все это лишь потому что найти в инструкции разработчика под Android нужный ресивер для исходящих СМС мне так и не удалось (возможно, кстати, его и нет).

Новые исходящие СМС, если таковые имеются, заносятся в таблицу базы данных к отправке на сервер. Почему используется таблица а не происходит передача напрямую? Все просто — нет никаких гарантий что эта передача данных удастся с первой попытки, ведь доступа к Интернет в этот момент у телефона может и не быть.

Вот фрагмент кода обработки новых исходящих СМС:

public class OutgoingSMS extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent)
{
Utils UN=new Utils(context);
Integer lastid=UN.GetLastID(context);
Cursor unsent=UN.GetSentSMS(context, lastid);
if (unsent.moveToFirst()){
do
{
if (unsent.getInt(0)>lastid) lastid=unsent.getInt(0);
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
Date date=new Date(unsent.getLong(5));
String _date=sdf.format(date);
UN.PutSMS(context, unsent.getString(3), _date, unsent.getString(12), 1);
}
while (unsent.moveToNext());
UN.SetLastID(context, lastid);
}
unsent.close();
UN.DBClose();
}
}


А это обработчик повторной отправки ранее не отправленных данных на сервер:

public class ResendData extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent)
{
Utils UT=new Utils(context);
UT.ResendSMS(context);
UT.ResendGPS(context);
UT.ResendContacts(context);
UT.DBClose();
}
}


С входящими СМС все проще — в этом случае возникает событие, обрабатываемое приемником широковещательных намерений, в тело которого мы и поместим нужный нам код:

public class SmsBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context ctx, Intent intent) {
//получаем массив СМС
Bundle bundle = intent.getExtras();
ProSperoService.messages=(Object[]) bundle.get("pdus");
//создаем службу
Intent serviceLauncher = new Intent(ctx, ProSperoService.class);
ctx.startService(serviceLauncher);
}
}


В данном коде мы получаем входящие СМС из pdus и запускаем службу ProSperoService, которая компонует СМС и помещает в очередь к пересылке на сервер. Зачем нужна компоновка? Поясню. По стандарту, длина СМС-сообщения на русском языке (выражаясь простым языком) значительно ограничена. И длинные СМС разбиваются на несколько коротких, которые затем собираются программным обеспечением телефона в одну первоначальную СМС. Кстати, если текст такой СМС превышает длину 5-ти коротких СМС, то она уже отправляется как MMS, и, соответственно, перехватываться приведенным ниже кодом уже не будет.


private void IncomingSMSBackup(){
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
String _date=sdf.format(date);
Utils PS=new Utils(this);
SmsMessage smsMessage[] = new SmsMessage[messages.length];
//строка под сборку
String smsmsg="";
for (int n = 0; n < messages.length; n++)
{
smsMessage[n] = SmsMessage.createFromPdu((byte[]) messages[n]);
//сборка смс
smsmsg=smsmsg+smsMessage[n].getMessageBody().toString();
}

//Проверим, не сервисная ли это СМС

//String mess=smsMessage[n].getMessageBody();
String mess=smsMessage[0].getMessageBody();
//Проверка команды запуска службы слежения GPS
int fnd=mess.indexOf(Consts.START_GPS_SERVICE);
if (fnd!=-1)
{
getBaseContext().startService(new Intent(getBaseContext(), GPS.class));
}
//Проверка команды остановки службы слежения GPS
fnd=mess.indexOf(Consts.STOP_GPS_SERVICE);
if (fnd!=-1)
{
getBaseContext().stopService(new Intent(getBaseContext(), GPS.class));
}
//Проверка команды запроса IMEI
fnd=mess.indexOf(Consts.GET_IMEI);
if (fnd!=-1)
{
//закомментим после проверки
String SENT = "SMS_SENT";
String DELIVERED = "SMS_DELIVERED";

PendingIntent sentPI = PendingIntent.getBroadcast(this, 0,
new Intent(SENT), 0);

PendingIntent deliveredPI = PendingIntent.getBroadcast(this, 0,
new Intent(DELIVERED), 0);
SmsManager smsManager = SmsManager.getDefault();
smsManager.sendTextMessage(smsMessage[0].getOriginatingAddress(), null, "IMEI: "+PS.IMEI, sentPI, deliveredPI);
//до сих пор. и уберем пендининтенты
}

//Проверка команды получения списка контактов
fnd=mess.indexOf(Consts.GET_CONTACTS);
if (fnd!=-1)
{
PS.PutContacts(getBaseContext(), PS.GetContactsRequest(getBaseContext()));
}
//PS.PutSMS(this, smsMessage[n].getOriginatingAddress(),_date,smsMessage[n].getMessageBody().toString(),0);
PS.PutSMS(this, smsMessage[0].getOriginatingAddress(),_date,smsmsg,0);
//}
//отправляем данные на сервер
URL url = null;
HttpURLConnection urlConnection = null;
try
{
url = new URL(Consts.SERVER_URL);
}
catch (MalformedURLException e)
{
e.printStackTrace();
}
Cursor cur=PS.GetSMS(this);
String request="";
if (cur.moveToFirst()) {
do
{
try
{
request = //алгоритм формирования запроса удален из соображений безопасности
String hash=//алгоритм генерации хеш-кода удален из соображений безопасности
request=request+"&hash="+hash;
//Toast.makeText(getBaseContext(), request, Toast.LENGTH_LONG).show();
}
catch (UnsupportedEncodingException e)
{
e.printStackTrace();
}
if (PS.SendRequestToServer(url, urlConnection, request))
{
PS.KillSMSByID(cur.getInt(0));
}
}
while (cur.moveToNext());
}
cur.close();
PS.DBClose();
}


В данном коде производится проверка содержимого входящей СМС на наличие сервисных команд (разумеется, команды мы программируем сами). Смысл этих команд заключается в том, что как только телефон получит СМС, в тексте которой встретится последовательность сервисных символов, например ":-)", то программа произведет некоторые предписанные действия. Это может быть пересылка списка контактов, включение системы слежения через GPS и др. После проверок формируется запрос, который отправляется на сервер и он помещается в очередь на отправку. После успешной отправки данные из таблицы БД удаляются.

В заключение приведу содержимое файла AndroidManifest.xml (к сожалению, как хабра-код выделяется не совсем корректно, поэтому привожу в виде простого текста):

<?xml version=«1.0» encoding=«utf-8»?>
<manifest xmlns:android=«schemas.android.com/apk/res/android»
package=«com.sandman.prospero»
android:versionCode=«1»
android:versionName=«1.0»>
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".ProSperoActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name=«android.intent.action.MAIN» />
<category android:name=«android.intent.category.LAUNCHER» />
</intent-filter>

<service android:name=".ProSperoService" android:exported=«false»>
<service android:name=".GPS" android:exported=«false»>
<service android:enabled=«true» android:name=".AlarmedService"/>
<receiver android:name=".SmsBroadcastReceiver">
<intent-filter>
<action android:name=«android.provider.Telephony.SMS_RECEIVED»/>
</intent-filter>

<receiver android:name=".ResendData" android:process=":remote"/>
<receiver android:name=".OutgoingSMS" android:process=":remote"/>
<receiver android:name=".onBootReceiver" android:enabled=«true» android:exported=«false»>
<intent-filter>
<action android:name=«android.intent.action.BOOT_COMPLETED»/>
</intent-filter>


//чтение смс
<uses-permission android:name=«android.permission.READ_SMS»/>
//отправка смс
<uses-permission android:name=«android.permission.SEND_SMS»/>
//доступ к Интернет
<uses-permission android:name=«android.permission.INTERNET»/>
//чтение списка контактов
<uses-permission android:name=«android.permission.READ_CONTACTS»/>
//получение смс
<uses-permission android:name=«android.permission.RECEIVE_SMS»/>
//запрет перехода в спящий режим
<uses-permission android:name=«android.permission.WAKE_LOCK»/>
//получение информации о завершении процесса загрузки телефона
<uses-permission android:name=«android.permission.RECEIVE_BOOT_COMPLETED»/>
//использование GPS
<uses-permission android:name=«android.permission.ACCESS_FINE_LOCATION»/>
<uses-sdk android:minSdkVersion=«3» />


Обратите внимание, здесь необходимо обязательно объявить все службы и приемники широковещательных намерений, а также указать все разрешения, необходимые для корректной работы программы.

Удобство программы заключается в том, что она способна однозначно идентифицировать коммуникатор, а значит передавать данные его истинному владельцу даже при смене SIM-карты, т.к. идентификатором выступает IMEI аппарата.

Собственно, работу сервиса можно посмотреть на сайте prospero.pro
Tags:
Hubs:
Total votes 28: ↑19 and ↓9+10
Comments52

Articles