FastPic Directизделие FPD-4.1 · паспорт
Приложение А · Вскрытие показало

Как устроен попандер

Сразу после тега картинки на странице fastpic лежит один <script> килобайт на тринадцать. В нём три функции. Первая ставит капкан на клики и проверяет, не копался ли кто в браузере. Вторая угоняет вкладку. Третья показывает картинку — но только если первые две отработали.

О методике. Всё ниже — наблюдения от 20.09.2026 за кодом, который сайт отдаёт любому посетителю. Имена переменных в оригинале случайные и меняются при каждой загрузке (dd2, ie6, sj5…), строки порезаны на куски. В примерах код переименован и причёсан для читаемости, логика сохранена. Серверную часть мы не видели и о ней не гадаем.

Общая схема

Вы открываете ссылкуfastpic.org/view/…Приходит HTML<img> без адреса + скрипт из трёх функцийФункция 1проверка: не трогал ли кто браузер;капкан на клики — на всю страницуКапкан взведён?state[4] === 1нетКартинки не будет<img> так и останется пустымдаФункция 3картинка получает адрес и появляетсяВы кликаете по картинкеили вообще куда угодноНовая вкладкато, что вы хотели: картинка покрупнееЭта вкладка → рекламная сетькуда именно — решает сеть, не вы и не сайт;ей же сообщают адрес вашей страницыФункция 2 сидит в засадеи ждёт вашего клика
Рис.Что происходит на странице просмотра без скрипта. Красным — путь, по которому уезжает ваша вкладка.без масштаба

Главное, что нужно понять про эту конструкцию: картинка у тега <img> изначально не имеет адреса. Вообще. Атрибута src нет:

<a href="https://fastpic.org/fullview/…" class="img-a" id="imglink" target="_blank">
    <img class="image img-fluid" title="Нажмите для увеличения изображения"/>
    <script>/* …13 КБ счастья… */</script>
</a>

Адрес появится, только если скрипт ниже решит, что вы заслужили. Отсюда и симптом, знакомый владельцам блокировщиков: реклама вырезана — а вместо картинки пустота.

Функция первая: капкан и детектор лжи

Начинается всё с проверки, не подменил ли кто-нибудь встроенные функции браузера. Именно так обычно работают блокировщики попандеров: подменяют window.open, addEventListener или preventDefault своими версиями. Поэтому скрипт спрашивает у браузера одно и то же тремя разными способами и сверяет показания, как следователь на перекрёстном допросе:

function looksNative(fn) {
    return Function.prototype.toString.call(fn).indexOf("native code") > -1;
}

function tampered(obj, prop) {
    var a = Reflect.getOwnPropertyDescriptor(obj, prop),
        b = Object.getOwnPropertyDescriptors(obj)[prop],
        c = Object.getOwnPropertyDescriptor(obj, prop);
    if (!a && !b && !c) return 0;
    if (!a || !b || !c) return 1;               // показания расходятся
    if (a.get !== b.get || a.get !== c.get ||
        a.value !== b.value || a.value !== c.value) return 1;
    return a.get && !looksNative(a.get) ? 1 : 0; // геттер есть, но он не родной
}

var mask = 0;
if (tampered(document, "dispatchEvent"))                    mask |= 1024;
if (tampered(window, "EventTarget"))                        mask |= 64;
if (tampered(window, "MouseEvent"))                         mask |= 512;
if (tampered(Event.prototype, "stopPropagation"))           mask |= 2;
if (tampered(Event.prototype, "preventDefault"))            mask |= 32;
if (tampered(Event.prototype, "stopImmediatePropagation"))  mask |= 16;
if (tampered(HTMLHtmlElement.prototype, "onclick"))         mask |= 4;
// …и сами инструменты допроса тоже проверяются: Function.prototype.toString,
//    Object.keys, Reflect, String.prototype.indexOf…
if (mask) state[14] = mask;                     // «в браузере копались»

Дальше скрипт прячет в заначку «чистые» версии методов события — на случай, если их подменят позже:

state[5] = Event.prototype.preventDefault;
state[6] = Event.prototype.stopImmediatePropagation;
state[7] = Event.prototype.stopPropagation;

И наконец ставит сам капкан — один обработчик сразу на четыре уровня, причём дважды: и свойством onclick, и слушателем в фазе перехвата, чтобы сработать раньше любого кода страницы:

function onAnyClick(ev) {
    if (ev && ev[MARK]) {                  // это наш собственный пробный клик
        if (!state[14]) state[4] = 1;      // «капкан взведён, вмешательства нет»
        ev.stopPropagation();
        return;
    }
    if (window.popHandler) window.popHandler(ev);   // настоящий клик — в попандер
}

[window, document, document.documentElement, document.body].forEach(function (t, i) {
    if (!t || state[i]) return;
    t.onclick = onAnyClick;
    if (t.onclick === onAnyClick && !state[14]) state[4] = 1;
    t.addEventListener("click", onAnyClick, { capture: true });
    state[i] = 1;
});

// Контрольный выстрел: шлём себе клик со случайной меткой и смотрим, долетит ли
if (!state[4]) {
    var probe = new Event("click");
    probe[MARK] = 1;
    document.currentScript.dispatchEvent(probe);
}

Запомните ячейку state[4]. Это флажок «капкан взведён, и никто не мешает». На нём держится всё остальное.

Вся функция выполняется дважды: сразу и ещё раз через setTimeout(…, 0), собранная заново из собственного текста через конструктор Function. Зачем — автор не сообщил; похоже на страховку от тех, кто вырезает или обезвреживает первый экземпляр.

Функция вторая: угон вкладки

Человек увлечённо смотрит в монитор с пейзажем, а за его спиной рука в полосатом рукаве подменяет второй монитор на экран с красными кляксами
Рис. без номера. Принцип tab-under в исполнении художника. Сходство с реальными рекламными сетями случайно.

Классический попандер открывает рекламу в новом окне под текущим. Здесь схема хитрее и называется tab-under: в новую вкладку отправляется то, что вы хотели, а реклама занимает вкладку, где вы только что были.

function popHandler(e) {
    // Только настоящий левый клик живого человека, без Ctrl/Alt/Meta
    if (!e.isTrusted || e.button > 0 || e.metaKey || e.ctrlKey || e.altKey) return;
    // Клики по рекламным iframe и кнопкам «закрыть» не трогаем: там свои порядки
    if (insideAdOrCloseButton(e.target)) return;

    var quota = readQuota();     // localStorage["_fpu5664332"] = "счётчик,время,соль"
    if (quota.count >= 5 && now() - quota.since < 300) return;   // не чаще 5 раз за 5 минут

    var link = nearestLink(e.target);
    var wanted = link ? link.href : location.href;   // куда вы на самом деле шли

    var w = window.open(wanted, "_blank");           // ваше — в НОВУЮ вкладку
    if (w) {
        state[5].call(e);                            // preventDefault из заначки
        state[6].call(e);                            // stopImmediatePropagation оттуда же
        w.opener = null;
        beacon("q1");
        bumpQuota();
        location.href = AD_URL;                      // а ЭТА вкладка уезжает к рекламодателю
    } else {
        /* всплывающие окна заблокированы — запасные планы, см. ниже */
    }
}

Вы кликнули по картинке, чтобы её увеличить. Картинка честно открылась в новой вкладке — вы довольны, смотрите. А вкладка, с которой вы пришли, уже на сайте рекламодателя, и обнаружите вы это, когда вернётесь. Фокус рассчитан на то, что виноватым вы назначите не fastpic.

Отдельного уважения заслуживает квота: не больше пяти угонов за пять минут. Грабить — так с чувством меры.

Запасные планы

Если window.open заблокирован, сдаваться никто не собирается. Тут в дело идёт ваш User-Agent — причём не тот, что сообщает браузер, а тот, что увидел сервер в заголовке запроса и вписал прямо в текст скрипта. По нему выбирается обходной манёвр:

  • Chrome и Firefox на ПК: создаётся невидимая ссылка, и по ней «кликают» синтетическим событием с зажатым Ctrl — так открывается фоновая вкладка в обход блокировщика всплывающих окон;
  • остальные: то же, но ссылкой с target="_blank"; после этого две вкладки договариваются между собой через localStorage, и старая уезжает на рекламу с задержкой до 12 секунд (в Chrome — четверть секунды);
  • совсем ничего не вышло: вас просто пускают, куда вы шли. Бывает и на нашей улице праздник.

Каждая ветка отчитывается на сервер маячком — картинкой /q1.gif, /q2.gif/qd.gif. Так владельцы видят, какой трюк в каком браузере ещё работает. Телеметрия у ребят поставлена лучше, чем у иного стартапа.

Что уезжает в рекламную сеть

Мы разобрали адрес попандера по параметрам. User-Agent туда не передаётся (он и так есть в заголовках). Зато передаётся кое-что поинтереснее:

ПараметрЗначениеЧто это
phttps://fastpic.org/view/…Полный адрес страницы, которую вы смотрите
idzone, cat, sub, type, tags5664332, 508, …Рекламная зона и категории
scr_infoaW5saW5lfHBvcE1hZ2ljfDc=base64 от inline|popMagic|7 — название и версия попандер-движка
cbслучайное числоЗащита от кэша
axkywwcfo, ghpnier, …случайное числоМусорный параметр со случайным именем — чтобы адрес нельзя было описать правилом фильтра

То есть сторонняя рекламная сеть узнаёт не только то, что вы зашли на fastpic, но и какую именно картинку вы открыли. Порядок параметров перемешивается при каждой загрузке, а в слове splash случайная буква заменяется процент-кодом: sp%6Cash.php, %73plash.php, spla%73h.php. Всё ради того, чтобы фильтры блокировщиков не могли зацепиться за постоянную строку.

Функция третья: «утром деньги — вечером стулья»

А вот и развязка. Именно здесь лежит адрес картинки — и условие, при котором вы его получите:

setTimeout(function () {
    var state = window.dd2;
    if (!state || !state[4]) return;      // капкан не взведён — картинки не будет

    img.src          = "https://i128.fastpic.org/preview/2026/…jpg?md5=…&expires=…";
    downloadBtn.href = "https://i128.fastpic.org/big/2026/…jpg?md5=…&expires=…&dl=1";
    // …плюс показать спрятанные блоки и вписать адрес в BB-коды для форумов
}, 0);

Три строчки, в которых вся бизнес-модель. Нет взведённого попандера — нет картинки. Заблокировали обработчик, подменили window.open, вырезали первую функцию — получите пустой тег <img> и любуйтесь.

Капкан на клики установленonclick + addEventListener, 4 уровняБраузер «не тронут»маска вмешательства = 0&state[4]img.src = адрескартинка появляетсяКартинка без попандерадля тех, кто обезвредил капканКонструкцией не предусмотрено
Рис.Логическая схема выдачи картинки. Обратите внимание: ветки «показать картинку без попандера» в ней нет.без масштаба

Вот почему наш скрипт не пытается «аккуратно отключить попандер и оставить всё остальное». Эта игра проиграна заранее: пока выполняется их код, правила пишут они. Скрипт вообще не даёт странице запуститься, а адрес оригинала достаёт из её текста — как из письма, которое читают, не вскрывая конверт. Как именно адрес там спрятан — в следующем приложении.

Итого по пунктам

  • Клик перехватывается на всей странице, на четырёх уровнях сразу, раньше любого другого кода.
  • Перед этим браузер проверяется на «вмешательство» — около двух десятков проверок встроенных функций.
  • Вкладку уводят на адрес, который выбирает сторонняя сеть; ей же сообщают, какую страницу вы смотрели.
  • Картинка — заложник: она появляется только при взведённом и исправном перехватчике.
  • Всё это обёрнуто в случайные имена и порезанные строки, которые меняются при каждой загрузке.

Ничего из этого не нужно, чтобы показать баннер. Баннеру достаточно тега <img> и совести.