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