Акселерометр — он же 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! Успейте зарегистрироваться на сайте мероприятия. В ближайшем будущем выйдет хабрастатья с анонсом программы, не пропустите!

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