Как разыграть соседа по комнате

в 13:59, , рубрики: linux, Ubuntu, Настройка Linux, Разработка под Linux, розыгрыш

От переводчика

Студент Университета Тафтса рассказывает, как подкалывал своего соседа по комнате. Он даже меня подколол, когда начал свою историю с того, что у них в общаге висит 4K-телевизор.

Введение

Перед тем, как я расскажу о том, как доводил несчастного Логана, я должен объяснить устройство медиа-системы в нашей комнате. Скоро вы поймёте, зачем.

Логан, если ты читаешь это, надеюсь, ты больше развлекался, чем нет.

Диспозиция

У нас стои́т комп с десктопной Убунтой, подключенный к телеку. Он выступает в роли медиа-сервера. Так как ему всё равно нужно постоянное интернет-подключение, там ещё крутится веб-сервер с парой страничек, SSH-сервер и ряд других сервисов.

В связи с тем, что телек 4K, а комп собран из того, что было под рукой, его видеокарта не тянет. Логан решил купить старую видюху NVIDIA, выпущенную пару поколений назад (которая всё же намного лучше, чем та, что была), чтобы нормально воспроизводить 4K-видео.

Рождение идеи

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

После некоторых поисков в инете стало ясно, что удалённо вызвать демонстрацию сообщения на телевизоре так же просто, как:

  1. Войти по SSH на комп под пользователем, под которым запущена трансляция
  2. DISPLAY=:0 zenity --info --text 'Привет!'

DISPLAY=:0 нужно, так как в моей сессии нет дисплея, а я хочу показать сообщение на основном экране.

Раз уж у нас были проблемы с видюхой NVIDIA, я решил остановиться на чём-то вроде:

DISPLAY=:0 zenity --warning --text 'Система работает в режиме слабой графики.'

Это работало, но каждый раз логиниться на сервер с помощью SSH-клиента, чтобы подоставать Логана — такое себе удовольствие. Поэтому я решил схитрить. Я подумал о задании в кроне, чтобы бесить его по расписанию, но тут была пара проблем:

  1. Собственно, регулярность
  2. У нас были другие задачи в кроне, что повышало риск раскрытия моего коварного задания

Другие варианты типа скрипта SysVInit были отброшены по тем же причинам. Поэтому я решил сделать публичную веб-страничку с кнопкой «Поиметь Логана».

Подготовка розыгрыша

Я прикинул, что мне понадобятся парочка вещей для начала:

  1. Что-то, обрабатывающее пользовательский ввод
  2. Что-то, выполняющее произвольные команды от имени веб-пользователя

Так я пришёл к:

  1. NGINX
  2. Расширение FPM для NGINX
  3. PHP
  4. Пакет FPM для PHP

В итоге я сделал сайт с лендингом logan.html и «страничкой действия» zenity.php:

logan.html
<!-- logan.html -->
<html>
    <head>
        <style type="text/css">
            form button {
                font-size: 20px;
            }

            div.explanation {
                width: 400px;
            }
        </style>

        <meta name="viewport"
              content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
        <meta name="HandheldFriendly" content="true">
    </head>
    <body>
        <form method="POST" action="/zenity.php">
            <button>Поиметь Логана</button>
        </form>
    </body>
</html>

Тут немного ерунды в тегах meta, чтобы адаптировать страничку для мобильников (помните, что я делаю это лёгким в использовании на ходу?) Для тех, кто не умеет рендерить HTML в голове, показываю, как это выглядит:

Как разыграть соседа по комнате - 1

Когда нажимается кнопка, POST-запрос улетает на другую страницу, которая делает всю грязную работу:

zenity.php

<?php
/* zenity.php */

$messages = Array(
    "Возникла ошибка.",
    "Возникла ошибка.",
    "Возникла ошибка.",
    "Возникла ошибка.",
    "Возникла ошибка.",
    "Возникла ошибка.",
    "Этому драйверу видеоадаптера не удаётся найти совместимое оборудование.",
    "Драйвер дисплея не отвечает.",
    "Драйвер дисплея не отвечает.",
    "Драйвер дисплея не отвечает.",
    "Драйвер дисплея не отвечает and has recovered.",
    "Система работает в режиме слабой графики.",
    "Система работает в режиме слабой графики.",
    "Система работает в режиме слабой графики.",
    "Система работает в режиме слабой графики.",
    "Система работает в режиме слабой графики.",
    "Система работает в режиме слабой графики.",
    "Драйвер NVIDIA не отвечает.",
    "NVIDIA остановила это устройство из-за ошибки. (код 43)",
    "Возникла ошибка на интерфейсе wlx10bef54d395c."
);

$statuses = Array("error", "warning");

$msg = $messages[array_rand($messages)];
$status = $statuses[array_rand($statuses)];
$timeout = "--timeout 10";

exec("sudo -u thedisplayuser /usr/sbin/zenity --$status --display=:0 --text 'Ошибка: $msg' $timeout > /dev/null &");

include 'logan.html';

?>

<div class="explanation">
Всплывающее окно с рандомным сообщением об ошибке только что появилось 
на домашнем телике и выбесило Логана, который ничего не знает об этом сайте.
Сейчас он офигевает, какого хрена у нас столько проблем с видюхой/интернетом/и т.д.
</div>
<br />
<img src='/logan.jpg' />

Эта страница делает ряд вещей:

  1. Выбирает случайное сообщение об ошибке
  2. Выбирает тип диалогового окна
  3. Демонстрирует сообщение в течение заданного периода времени (10 секунд)
  4. Рисует кнопку и объясняет, что происходит — всё в сопровождении забавной фотки Логана собственной персоной

Для тех, кто всё ещё не умеет рендерить HTML в голове (снова, надеюсь, большинство людей), страница выглядит так:

Как разыграть соседа по комнате - 2

Если вы удивлены, почему веб-странице позволено исполнять код таким образом, и почему владелец сессии веб-сервера (www-data) может выполнять команды как пользователь дисплея (thedisplayuser), вы, возможно, будете счастливы узнать, что я строго ограничил это в файле sudoers:

# /etc/sudoers
www-data ALL=(thedisplayuser) NOPASSWD: /usr/bin/zenity

Эта конкретная часть конфигурации позволяет www-data запускать только /usr/bin/zenity как thedisplayuser без пароля. Я сделал это после того, как намаялся с дурацкими ошибками в настройках PHP и NGINX. Затем я отправил URL паре друзей в кампусе, которые знают Логана.

Результат розыгрыша

Хвала небесам, Логан отреагировал так бурно, как он это умеет. Если бы он был слегка раздражён, то мысль о том, что мои усилия потрачены впустую, раздражала бы уже меня. Но нет! Он потерял самообладание. Не могу сосчитать, сколько было ребутов, переустановок драйвера и модификаций ядра. Я жалею лишь, что не заснял на видео, как он взбесился после запуска VLC, когда кто-то выкинул кучу окошек с сообщениями об ошибках видеокарты.

Но я слишком увлёкся, и Крис, ещё один наш сосед по комнате, решил вмешаться…

Охота на охотника

Первый этап

Одним прекрасным днём, когда Логан спал, я заметил сообщение об ошибке, которое гласило: «За всем этим стоит Макс». Чоооо? Я получил пранкобраточку! Итак, я стал разбираться и выяснил, что кто-то (Крис) внёс эту фразу в набор случайных сообщений в zenity.php. Я по-быстрому удалил её (пока Логан не прочухал, что его разыгрывают) и решил, что веселье закончилось. Не тут-то было.

Второй этап

Спустя неделю или около того сообщение вылезло снова. Я подумал, что Крис спалил меня и заново добавил его в список. Ни фига. Его там не было. После тщательного изучения файла я обратил внимание, что он теперь называется /usr/sbin/zenity вместо /usr/bin/zenity (системный по-умолчанию), и в sudoers была соответствующая разрешающая запись. Так что это за /usr/sbin/zenity? Шелл-скрипт:

#!/bin/bash

echo '.' >> /tmp/log.txt
if [ 0 -eq $((RANDOM % 100)) ];
then /usr/bin/zenity --error --display=:0 --text "За этим стоит Макс." --timeout 10 > /dev/null &
else /usr/bin/zenity "$@"
fi

Что ж, это было гадство следующего уровня, если я вообще встречал подобное. 99% времени всё работало, как надо, а в оставшийся один процент выскакивало сообщение «За этим стоит Макс». Я удалил файл (знаю, не стоило) и запись в sudoers, привёл zenity.php в исходный вид. Сообщения перестали появляться. Но потом они вернулись.

Третий этап

Я проверил zenity.php. Ничего нового. /usr/sbin/zenity? Исчез. Я обескуражен. Тогда я решил заглянуть внутрь /usr/bin/zenity:

#!/bin/bash
# --- ВСЯКИЙ БИНАРНЫЙ МУСОР ---
# --- ВСЯКИЙ БИНАРНЫЙ МУСОР ---
# --- ВСЯКИЙ БИНАРНЫЙ МУСОР ---
# --- ВСЯКИЙ БИНАРНЫЙ МУСОР ---
# --- ВСЯКИЙ БИНАРНЫЙ МУСОР ---
if [ 0 -eq $((RANDOM % 70)) ];
then /usr/bin/rpmdb-client --error --display=:0 --text "З""а"" ""э""т""им ""с""т""о""и""т"" ""М""а""к""с." --timeout 10 > /dev/null &
else /usr/bin/rpmdb-client "$@"
fi

Маленький хитрый гадёныш. Он исправил бинарник zenity, сделав из него баш-скрипт, который срабатывает 1 раз и 70. Какого чёрта? И что это вообще за rpmdb-client? Итак, я дал отпор, изменив его:

# <ОБРЕЗАНО>
if [ 0 -eq $((RANDOM % 70)) ];
then /usr/sbin/rpmdb-client --error --display=:0 --text "З""а"" ""э""т""им ""с""т""о""и""т"" ""М""а""к""с." --timeout 10 > /dev/null &
else /usr/bin/rpmdb-client "$@"
fi

Уловили разницу? В превом случае вызывается /usr/sbin/rpmdb-client вместо /usr/bin/rpmdb-client, который запускает ничего не делающий баш-скрипт. При достаточном везении он не заметит лишнего символа и его сообщение никогда не появится.

TODO: Разобраться с разницей между исполняемым ELF-файлами /usr/sbin/zenity и /usr/bin/rpmdb-client, который создал Крис. В бинарниках есть какое-то странное отличие, которое я пока не понял.

Четвёртый этап

Я решил укрепить оборону, пока Крис не заметил разницу в одну букву, описанную выше. Я отменил все свои изменения и решил вместо этого пропатчить zenity. Глубокая признательность Тому Хеббу (как и в каждом моём техническом посте) за помощь с этим. Вот что я сделал:

  1. Настроил apt на загрузку исходников (в данном случае, добавив deb-src в /etc/apt/sources.list)
  2. apt-get source zenity
  3. Сделал патч с quilt:
    1. quilt new myPatch.diff
    2. Патч для src/msg.c, который определяет наличие слова «Макс» в тексте сообщения:
      msg.c

      Index: zenity-3.18.1.1/src/msg.c
      ===================================================================
      --- zenity-3.18.1.1.orig/src/msg.c
      +++ zenity-3.18.1.1/src/msg.c
      @@ -21,6 +21,8 @@
        * Authors: Glynn Foster <glynn.foster@sun.com>
        */
              
      +#include <string.h>
      +
       #include "config.h"
              
       #include "zenity.h"
      @@ -85,6 +87,11 @@ zenity_msg (ZenityData *data, ZenityMsgD
         GObject *text;
         GObject *image;
              
      +  if (strstr(msg_data->dialog_text, "Max")
      +      || strstr(msg_data->dialog_text, "max")) {
      +      return;
      +  }
      +
         switch (msg_data->mode) {
           case ZENITY_MSG_WARNING:
             builder = zenity_util_load_ui_file ("zenity_warning_dialog", NULL);
    3. quilt add src/msg.c
    4. quilt pop
  4. dpkg-source --commit
  5. dpkg-buildpackage -us -uc
  6. Осознал, что создание и установка нового пакета — это более палевно, чем подмена бинарника
  7. По-тихому заменил бинарник rpmdb-client (его zenity) моей версией
  8. Похлопал себя по спине

Этот патч меняет поведение zenity таким образом, что, как только в тексте сообщения появляется слово «Макс», приложение по-тихому ничего не делает.

Заключение

До конца марта Крис так и не узнал, что я подправил бинарник. В результате Логан не увидел ни одного сообщения с моим именем. Так что я решил продолжать розыгрыш до тех пор, пока я ему всё не расскажу после окончания курса. Но когда занятия закочились, мы разъехались. Хотя мы с Логаном снова будем жить вместе в следующем году, скорее всего мы не будем столько времени проводить в комнате, чтобы розыгрыш имел смысл. Поэтому я решил опубликовать этот пост перед тем, как устраивать новые проделки.

Автор: Michae1

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js