10 Июня, 2019

Шифр ускорением: изучаем акселерометр Android-устройства на примере задания NeoQUEST-2019

НеоБИТ


Акселерометр — он же G-сенсор — является одним из самых распространенных датчиков на сегодня. Встретить его можно практически в каждом современном гаджете. Акселерометр выполняет довольно простую задачу — измеряет ускорение устройства. Давайте посмотрим, как он это делает — разберем механизмы сенсоров API Android на примере задания №7 из online-этапа NeoQUEST-2019.

По легенде нам выдано 2 файла: 7.apk и 7.txt. Из текста задания (а все задания по-прежнему доступны тут ) можно сделать следующие выводы: 7.apk – программа-шифратор, которая каким-то образом использует параметры акселерометра устройства; 7.txt – криптограмма, сгенерированная шифратором. Записано в ней следующее:

[1749054104147639][2.07154922][10.001905][4.5387093][1749056073889025][5.7193284][8.221763][0.01391537][1749058029180773][4.684068][12.05614][0.0377285][1749060105291613][4.6900544][6.9307165][4.7094293][1749062123327502][4.4682417][7.512769][6.037215][1749067640096818][1.0396843][8.798672][4.9335976][1749070016073380][2.3173676][10.180047][4.948362][1749072343679582][4.3660607][12.218135][0.5312999][1749073674459611][2.48394698][10.834006][6.306282][1749075827770391][0.2795044][13.279829][0.19391555]

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

[1749054104147639][-2.07154922][10.001905][4.5387093]
[1749056073889025][5.7193284][8.221763][0.01391537]
[1749058029180773][-4.684068][12.05614][0.0377285]
[1749060105291613][4.6900544][6.9307165][-4.7094293]
[1749062123327502][4.4682417][7.512769][6.037215]
[1749067640096818][1.0396843][8.798672][-4.9335976]
[1749070016073380][-2.3173676][10.180047][4.948362]
[1749072343679582][-4.3660607][12.218135][0.5312999]
[1749073674459611][-2.48394698][10.834006][-6.306282]
[1749075827770391][0.2795044][13.279829][-0.19391555]


С форматом криптограммы разобрались, но что это за значения — неизвестно. Какие-то параметры акселерометра устройства, без всякой конкретики. А давайте-ка зайдем на сайт разработчиков Android и посмотрим , что вообще акселерометр может показать.

Видим следующее описание:



Выяснили, за что отвечают параметры с плавающей точкой — это ускорение устройства вдоль осей X, Y и Z. Но как понять, за какую ось отвечает каждый из них? Самое время запускать приложение. Оно выглядит следующим образом:



Здесь есть 2 возможности определения поведения приложения: декомпиляция .apk и анализ получаемых значений. Далее рассмотрим второй способ и приведем вставки декомпилированного кода, отвечающего за рассматриваемые действия приложения.

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

public class MotionSnapshot {     public final float angle_alpha;     public final float angle_beta;     public final float angle_gamma;     public final long  event_time;     ... }


Исходя из данных рассуждений, перед нами возникают следующие задачи:

1. Определить, какому значению в криптограмме соответствует наклон устройства
2. Определить шаблон отклонений
3. Каждой цифре поставить в соответствие шаблон отклонения для расшифрования

Первые 2 задачи будем выполнять параллельно. На телефоне мы можем тестировать 2 вида наклона:

1. От себя/На себя
2. Влево/Вправо



Код, отвечающий за обработку событий от акселерометра
public class SensorListener implements SensorEventListener {     private MotionTrace  Trace;      public SensorListener(MotionTrace trace, MainActivity activity)     {         Trace = trace;     }      @Override     public void onAccuracyChanged(Sensor sensor, int accuracy)     {      }      @Override     public void onSensorChanged(SensorEvent event)     {         Trace.addSnaphot(new MotionSnapshot(event.values, event.timestamp));     } }



Код, отвечающий за генерацию трассы наклонов устройства во время нажатия кнопки
public class MotionTrace {     private ArrayList<MotionSnapshot> Deltas;     private long Length;     private MotionSnapshot LastSnapshot;      public MotionTrace(long len)     {         LastSnapshot = new MotionSnapshot(0,0,0,0);         Deltas = new ArrayList<>();         Length = len;     }     public void addSnaphot(MotionSnapshot snapshot)     {         if (Deltas.size() >= Length)         {             Deltas.remove(0);         }          MotionSnapshot delta = new MotionSnapshot(0,0,0,0);          if (Deltas.size() > 0)         {             delta = snapshot;         }         Deltas.add(delta);     }     public ArrayList<MotionSnapshot> getDeltas()     {         return new ArrayList<>(Deltas);     } } 




Код, отвечающий за генерацию криптограммы
public void SaveCiphertext() {     Log.d(Config.MAIN_TAG, "SAVING - {{" + Ciphertext + "}}");     try     {         File root = new File(Environment.getExternalStorageDirectory(), Config.DIRNAME);         if (!root.exists())         {             Log.d(Config.MAIN_TAG, "Creating directory - [" + root + "]");             if (!root.mkdirs())             {                 Log.d(Config.MAIN_TAG, "Error creating directory");             }         }         File out_file = new File(root, Config.FILENAME);         Log.d(Config.MAIN_TAG, "Out - [" + out_file + "]");         PrintWriter writer = new PrintWriter(out_file, "UTF-8");         writer.println(Ciphertext);         writer.close();          Toast.makeText(this, "Saved to - [" + out_file + "]", Toast.LENGTH_LONG).show();     }     catch (IOException ex)     {         Toast.makeText(this, "Error saving data", Toast.LENGTH_SHORT).show();     }     Ciphertext = ""; }



Начнем по порядку. Для тестирования наклонов первого типа выберем цифры 2 и 8. Нажмем на каждую по 3 раза с возрастающим усилием. Получаем следующий результат:

[2687418463227102][-0.23700714][10.764615][-0.9759079]
[2687419411042043][-3.5834892][13.591138][-1.7036858]
[2687420383026907][-5.575793][13.533228][-1.3104248]
[2687421461360546][0.6850295][6.0002656][0.5568123]
[2687422317256542][4.1720495][1.8675026][1.545407]
[2687423250514599][7.9689393][-3.600097][0.33846742]


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

[? ]
[ (< 0) — отклонение от себя; (> 0) — отклонение на себя ]
[?]
[?]


Аналогично проведем тесты с кнопками 4 и 6. Результаты:

[2688019191605386][1.7270225][9.541045][0.0397171]
[2688020247971353][1.0615791][9.794326][4.9135437]
[2688021887957875][1.0974716][7.5535636][7.8307548]
[2688023749896352][1.3328063][9.43923][-0.27600938]
[2688024849688832][1.1357567][9.9313135][-2.4410355]
[2688026002520864][0.30400848][6.4610033][-8.0956335]


Обновим шаблон с учетом исследованной закономерности:

[? ]
[ (< 0) — отклонение от себя; (> 0) — отклонение на себя ]
[?]
[ (< 0) — отклонение вправо; (> 0) — отклонение влево]


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

Теперь создадим шаблон для каждой цифры на клавиатуре, исходя из положения кнопок и разработанного шаблона (* — параметр нас не интересует):

1 – [*][ < 0 ][*][> 0]
2 – [*][< 0][*][ близко к 0]
3 – [*][< 0][*][< 0]
4 – [*][][*][]
5 – [*][близко к 0][*][близко к 0]
6 – [*][близко к 0][*][< 0]
7 – [*][> 0][*][> 0]
8 – [*][> 0][*][близко к 0]
9 – [*][> 0][*][< 0]
0 – [*][> 0][*][близко к 0]


Как видно из рисунка, кнопки 8 и 0 имеют одинаковые параметры, поэтому их расшифрование может быть неоднозначным — при встрече такой комбинации в криптограмме следует попробовать оба варианта. Теперь применим полученные шаблоны к криптограмме, и получим 2 варианта ответа: 1029761235 и 1829761235, верным из которых является 1829761235. Задание пройдено!

Уже совсем скоро — 26 июня — состоится «Очная ставка» NeoQUEST 2019! Успейте зарегестрироваться на сайте мероприятия . В ближайшем будущем выйдет хабрастатья с анонсом программы, не пропустите!