Данные выборов получили, теперь деобфусцируем и очищаем

В 2021 году Центризбирком РФ обфусцировал статистические данные выборов на своем сайте. Несколько дней назад я сделал и выложил в комментариях к новости на хабре деобфускатор, чтобы помочь исследователям обнаружить статистические аномалии в результатах. Сегодня взял день отпуска, написать этот пост и поделиться очищенными данными в формате sqlite по по федеральному избирательному округу, которыми поделился @illusionofchaosв посте Получаем данные результатов выборов с сайта Центризбиркома РФ

Под катом я предлагаю еще раз посмотреть, какими техническими методами затрудняли анализ программисты сайта ЦИК-а. Новых идей там нет, решения примитивные , даже обидно что кто-то за них квартиру получил[*]. Вся работа по написанию деобфускатора заняла меньше рабочего дня (точнее вечера + полночи). Основная цель этой статьи не в описании методов, а дополнительный анонс деобфускатора для исследователей. Обсуждать решение применить методики запутывания на государственном сайте куда интереснее, но этим бессмысленно заниматься в интернете.

Итак, они реализовали четыре метода:

Лишние элементы

На место в DOM, где дожно находиться просто число с результатам добавляют дополнительные элементы, которые отрисовываются где-то за пределами окна браузера. 

image-loader.svg

Единственный интересный момент тут это то, что иногда примешиваются синтаксически неверные правила css у элементов с правильным данными. Самый интересный пример, что я видел, это font-size: 0am. Видимо расчёт, что люди будут использовать регулярные выражения для считывания этих правил. В деобфускаторе используется честный парсер CSS и поэтому он не восприимчив к таким трюкам.

image-loader.svg

JavaScript DOM modification

Страница с результатами с сервера приходит еще более грязная, чем есть в DOM, которые видим в Developer Tools. Дополнительная мелкая подножка есть в JavaScript.

Там определены три функции, которые либо переставляют ячейки местами в таблице, либо убирают один символ, либо просто ставят результат в ячейку. 

image-loader.svg

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

image-loader.svg

В деобфускаторе я это обошёл просто реализовав эти функции на C#.

CSS: after

Для некоторых элементов контент записывается в CSS стиле. 

image-loader.svg

Парсим CSS и меняем значение элемента с такими стилями — скучно.

Игры со шрифтами

Самым интересным для меня была игра со шрифтами.

На скриншоте видно, что Ь отрисовывается как цифра 2. 

image-loader.svg

Если открыть этот шрифт в Windows Font Viewer то будет отображаться полная ерунда. Что же тут произошло?

image-loader.svg

ЦИК нагенерировал 100 шрифтов (узнал это из данных от @illusionofchaos) в которых по только им известному правилу изменили правила,  по которым отображаются коды символов в глифы,  которые определены внутри шрифта и определяют то,  как графема будет отрисована. 

В Open Font это определяется в таблице cmap. Эта таблица используется для того, чтобы можно было переиспользовать глифы для разных букв. Например, латинская A может иметь такие же правила отрисовки, как и русская А.

image-loader.svg

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

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

Заключение

Я проверил и оптимизировал деобфускатор. Он работает примерно полчаса на ноутбуке на данных по федеральному округу, которые предоставил illusionofchaos на GitHub. Эти же данные, прогнанные через деобфускатор, можно тоже скачать на GitHub

Сейчас сделал проект Schwabra, для переброса этих данных в sqlite.
Я очень бегло проверил валидность данных, общая сумма по УИК-ам выглядит похожей. Данные в sqlite формате тоже там же

Краткая проверка правдоподобности

Данные ЦИК-а: izbirkom.ru/region/region/karachaev-cherkess? action=show&root=1&tvd=100100225883177&vrn=100100225883172®ion=9&global=&sub_region=9&prver=0&pronetvd=null&vibid=100100225883177&type=242

Данные из sqlite

SELECT num, title, SUM(value)
FROM station
         JOIN result ON station.id = result.StationId
WHERE name LIKE '%УИК%'
GROUP BY num
ORDER BY num;

SELECT num, title, value
FROM station
         JOIN result ON station.id = StationId
WHERE name = 'ЦИК России'
ORDER BY num

1

Число избирателей, внесенных в список избирателей на момент окончания голосования

108171434

2

Число избирательных бюллетеней, полученных участковой избирательной комиссией

98614022

3

Число избирательных бюллетеней, выданных избирателям, проголосовавшим досрочно

199064

4

Число избирательных бюллетеней, выданных в помещении для голосования в день голосования

47386185

5

Число избирательных бюллетеней, выданных вне помещения для голосования в день голосования

8081206

6

Число погашенных избирательных бюллетеней

42946873

7

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

8272698

8

Число избирательных бюллетеней, содержащихся в стационарных ящиках для голосования

47243246

9

Число недействительных избирательных бюллетеней

1167957

10

Число действительных избирательных бюллетеней

54347987

11

Число утраченных избирательных бюллетеней

1201

12

Число избирательных бюллетеней, не учтенных при получении

507

13

1. Политическая партия «КОММУНИСТИЧЕСКАЯ ПАРТИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ»

10558234

14

2. Политическая партия «Российская экологическая партия «ЗЕЛЁНЫЕ»

500671

15

3. Политическая партия ЛДПР — Либерально-демократическая партия России

4185051

16

4. Политическая партия «НОВЫЕ ЛЮДИ»

2946703

17

5. Всероссийская политическая партия «ЕДИНАЯ РОССИЯ»

27626893

18

6. Партия СПРАВЕДЛИВАЯ РОССИЯ — ЗА ПРАВДУ

4139640

19

7. Политическая партия «Российская объединенная демократическая партия «ЯБЛОКО»

744065

20

8. Всероссийская политическая партия «ПАРТИЯ РОСТА»

286598

21

9. Политическая партия РОССИЙСКАЯ ПАРТИЯ СВОБОДЫ И СПРАВЕДЛИВОСТИ

425677

22

10. Политическая партия КОММУНИСТИЧЕСКАЯ ПАРТИЯ КОММУНИСТЫ РОССИИ

707968

23

11. Политическая партия «Гражданская Платформа»

85716

24

12. Политическая партия ЗЕЛЕНАЯ АЛЬТЕРНАТИВА

349820

25

13. ВСЕРОССИЙСКАЯ ПОЛИТИЧЕСКАЯ ПАРТИЯ «РОДИНА»

426359

26

14. ПАРТИЯ ПЕНСИОНЕРОВ

1364592

Сам деобфускатор https://github.com/ulex/izbirkom21

© Habrahabr.ru