Как устроен попандер
Сразу после тега картинки на странице fastpic лежит один <script> килобайт на тринадцать. В нём три функции. Первая ставит капкан на клики и проверяет, не копался ли кто в браузере. Вторая угоняет вкладку. Третья показывает картинку — но только если первые две отработали.
dd2, ie6, sj5…), строки порезаны на куски. В примерах код переименован и причёсан для читаемости, логика сохранена. Серверную часть мы не видели и о ней не гадаем.Общая схема
Главное, что нужно понять про эту конструкцию: картинка у тега <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: в новую вкладку отправляется то, что вы хотели, а реклама занимает вкладку, где вы только что были.
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 туда не передаётся (он и так есть в заголовках). Зато передаётся кое-что поинтереснее:
| Параметр | Значение | Что это |
|---|---|---|
p | https://fastpic.org/view/… | Полный адрес страницы, которую вы смотрите |
idzone, cat, sub, type, tags | 5664332, 508, … | Рекламная зона и категории |
scr_info | aW5saW5lfHBvcE1hZ2ljfDc= | 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> и любуйтесь.
Вот почему наш скрипт не пытается «аккуратно отключить попандер и оставить всё остальное». Эта игра проиграна заранее: пока выполняется их код, правила пишут они. Скрипт вообще не даёт странице запуститься, а адрес оригинала достаёт из её текста — как из письма, которое читают, не вскрывая конверт. Как именно адрес там спрятан — в следующем приложении.
Итого по пунктам
- Клик перехватывается на всей странице, на четырёх уровнях сразу, раньше любого другого кода.
- Перед этим браузер проверяется на «вмешательство» — около двух десятков проверок встроенных функций.
- Вкладку уводят на адрес, который выбирает сторонняя сеть; ей же сообщают, какую страницу вы смотрели.
- Картинка — заложник: она появляется только при взведённом и исправном перехватчике.
- Всё это обёрнуто в случайные имена и порезанные строки, которые меняются при каждой загрузке.
Ничего из этого не нужно, чтобы показать баннер. Баннеру достаточно тега <img> и совести.