Данные выборов получили, теперь деобфусцируем и очищаем
В 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