Шифр ускорением: изучаем акселерометр 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! Успейте зарегистрироваться на сайте мероприятия. В ближайшем будущем выйдет хабрастатья с анонсом программы, не пропустите!
    НеоБИТ
    68,79
    Компания
    Поделиться публикацией

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

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое