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

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

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

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

В деобфускаторе я это обошёл просто реализовав эти функции на C#.
CSS ::after
Для некоторых элементов контент записывается в CSS стиле.

Парсим CSS и меняем значение элемента с такими стилями — скучно.
Игры со шрифтами
Самым интересным для меня была игра со шрифтами.
На скриншоте видно, что Ь
отрисовывается как цифра 2.

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

ЦИК нагенерировал 100 шрифтов (узнал это из данных от @illusionofchaos) в которых по только им известному правилу изменили правила, по которым отображаются коды символов в глифы, которые определены внутри шрифта и определяют то, как графема будет отрисована.
В Open Font это определяется в таблице cmap. Эта таблица используется для того, чтобы можно было переиспользовать глифы для разных букв. Например, латинская A
может иметь такие же правила отрисовки, как и русская А
.

Эту таблицу ЦИК и заменил в каждом из сгенерированных шрифтов по (предположительно) случайным правилам. Это перемешивание не сложно обратить. У каждого глифа есть правила, по которым он отрисовывается. Если один раз применить OCR или вручную разметить символы, и сравнивая эти правила рисования можно однозначно сказать, какая это графема. Но к моей большой радости, нашлось более простое и изящное решение. На сайте был доступен оригинальный шрифт :)
Посколько таблица глифов осталась в том же порядке, что и была до перемешивания, то, имея на руках оригинальный шрифт, можно обратить это перемешивание. В оригинальном шрифте мы можем найти индекс в таблице глифов для любой интересующей нас буквы и использовать этот индекс для понимания того, что изображено на глифе. Строго говоря, это не всегда возможно, но поскольку для перемешивания были выбраны только цифры, то это отображение биективно.
Заключение
Я проверил и оптимизировал деобфускатор. Он работает примерно полчаса на ноутбуке на данных по федеральному округу, которые предоставил illusionofchaos на GitHub . Эти же данные, прогнанные через деобфускатор, можно тоже скачать на GitHub
Сейчас сделал проект Schwabra, для переброса этих данных в sqlite.
Я очень бегло проверил валидность данных, общая сумма по УИК-ам выглядит похожей. Данные в sqlite формате тоже там же
Краткая проверка правдоподобности
Данные из 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
Автор: Александр Улитин