CryptoHack. Решение ECB Oracle

В этой статье я расскажу о режиме шифрования ECB и покажу каким образом можно расшифровывать произвольный текст, при наличии возможности добавлять известный открытый текст к данным. Данная статья является расширением ранее опубликованной мной статьи на Medium.

Задача

Для начала посмотрим на саму задачу, а после я попробую разобрать составляющие части и подробно описать решение. Итак, в задаче нам предоставляют ссылку на страницу https://aes.cryptohack.org/ecb_oracle/

f2e79ce863c2d57fab4e36ce6d58b087.png

Здесь есть исходный код сервера

bc43bb5f463f00b6e6d62ac5a187b44b.png

И возможность взаимодействия с ним

8cdec28b77ce1aa317fd7035088e2564.png

Также мы можем взаимодействовать с сервером программно через GET запросы.

Суть задачи — найти значение FLAG.

Курим код

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad


KEY = ?
FLAG = ?


@chal.route('/ecb_oracle/encrypt//')
def encrypt(plaintext):
    plaintext = bytes.fromhex(plaintext)

    padded = pad(plaintext + FLAG.encode(), 16)
    cipher = AES.new(KEY, AES.MODE_ECB)
    try:
        encrypted = cipher.encrypt(padded)
    except ValueError as e:
        return {"error": str(e)}

    return {"ciphertext": encrypted.hex()}</code></pre>

<p>Сервер поддерживает только одну функцию&nbsp;— <code>encrypt</code>, в&nbsp;которую мы можем передать произвольный <code>plaintext</code>. При&nbsp;получении текста сервер пытается преобразовать его из&nbsp;хекс-строки в&nbsp;байты. Дальше важно&nbsp;— к&nbsp;переданному нами тексту присоединяется флаг, и&nbsp;к&nbsp;этому всему применяют <code>pad</code>(то&nbsp;есть по&nbsp;сути дополняют в&nbsp;конце так, чтобы результат делился нацело на&nbsp;блоки по&nbsp;16&nbsp;байт). </p>

<p>Почему 16? Потому что&nbsp;это размер блока алгоритма шифрования AES, с&nbsp;помощью которого значение <code>padded</code> шифруется в&nbsp;режиме ECB. Результат шифрования в&nbsp;формате хекс-строки возвращается к&nbsp;нам.</p>

<h2>Разбираемся с&nbsp;понятиями</h2>

<p>Как&nbsp;работает AES я&nbsp;здесь рассказывать не&nbsp;стану. Во-первых, это большая тема и&nbsp;требует отдельной статьи. Во-вторых, в&nbsp;данной задаче нам совершенно всё равно как&nbsp;он работает. Уязвимость, которую мы будем использовать, применима к&nbsp;любому блочному шифру. Или, если говорить строже, то&nbsp;к&nbsp;любому шифру, который можно считать функцией</p>

<p><img alt="E_k(T) = C" src="https://habrastorage.org/getpro/habr/upload_files/57f/edf/4b5/57fedf4b5dd3b1ed5b1a25d5e85e236a.svg" /></p>

<p>(где <img alt="E_k(T)" src="https://habrastorage.org/getpro/habr/upload_files/e64/0b9/dbb/e640b9dbb7c304635178f8782049767e.svg" />&nbsp;— шифрование текста <img alt="T" src="https://habrastorage.org/getpro/habr/upload_files/7ae/fbc/798/7aefbc798eff73b11186bffb4de873f5.svg" />, с&nbsp;помощью ключа <img alt="k" src="https://habrastorage.org/getpro/habr/upload_files/d2e/b43/4b2/d2eb434b2a3363f990fffa652949d1e5.svg" />, <img alt="C" src="https://habrastorage.org/getpro/habr/upload_files/879/e8f/0e2/879e8f0e2ed80a5f24827e912c79e5c3.svg" />&nbsp;— зашифрованное сообщение), такой, что&nbsp;<img alt="\forall k_1 = k_2, T_1 = T_2 \implies C_1 = C_2" src="https://habrastorage.org/getpro/habr/upload_files/85c/10e/ae9/85c10eae9e555df6818c5a86a3264467.svg" />. Использовать мы будем как&nbsp;раз это свойство.</p>

<p>А&nbsp;вот что&nbsp;стоит разобрать подробно, так это режим шифрования ECB.</p>

<h2>ECB в&nbsp;деталях</h2>

<p>Блочные шифры (AES, DES и&nbsp;прочие) по&nbsp;своему устройству способны шифровать только блоки определённой длины, и&nbsp;надёжность таких шифров определяется исходя из&nbsp;надёжности шифрования одного блока. Конечно, существуют методы криптоанализа, основанные на&nbsp;анализе шифрования большого количества блоков. Однако в&nbsp;них каждый блок шифруется своим ключом.</p>

<p>Короче говоря, блочные шифры задуманы так, что&nbsp;вы берёте один блок текста и&nbsp;шифруете его. Если нужно зашифровать ещё&nbsp;блок, то&nbsp;вы берёте другой ключ, независимый от&nbsp;первого, и&nbsp;шифруете опять, и&nbsp;так далее. Вообще всякий раз, когда нужно что-то&nbsp;зашифровать, вам нужен новый ключ. Только в&nbsp;такой ситуации авторы шифров гарантируют надёжность (ну или&nbsp;хотя бы пытаются гарантировать).</p>

<p>Конечно, на&nbsp;практике мы хотим шифровать произвольные объёмы данных, к&nbsp;тому же используя один ключ, или&nbsp;менять ключи, но&nbsp;не&nbsp;для&nbsp;каждого блока&nbsp;— создание и&nbsp;передача надёжных ключей по&nbsp;сети это отдельная головная боль. Для&nbsp;решения этой проблемы придумали режимы шифрования, их&nbsp;существует много и&nbsp;у&nbsp;каждого есть свои достоинства и&nbsp;недостатки (и&nbsp;уязвимости). В&nbsp;данной статье поговорим про&nbsp;ECB.</p>

<p>ECB (Electronic codebook)&nbsp;— самый простой режим шифрования. В&nbsp;этом режиме сообщение просто разбивается на&nbsp;блоки, каждый из&nbsp;которых шифруется независимо от&nbsp;остальных. </p>

<p><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/de8/a46/010/de8a460105cba167e9b9b455f01b0241.png" alt="Схема работы ECB" /></p>

<p>Схема работы ECB</p>

<p>Всё делается одним ключем и&nbsp;в&nbsp;этом большая проблема, потому что&nbsp;в&nbsp;таком случае одинаковые блоки открытого текста превращаются в&nbsp;одинаковые зашифрованные блоки. Крупные паттерны открытого текста при&nbsp;этом просачиваются в&nbsp;зашифрованные данные, что&nbsp;сильно упрощает криптоанализ. В&nbsp;статье на&nbsp;Википедии есть чудесный пример этого эффекта: </p>

<p><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/92b/034/011/92b0340114063e43c0c6e5045656bbf1.png" alt="Изображение зашифрованное AES в режиме ECB" /></p>

<p>Изображение зашифрованное AES в&nbsp;режиме ECB</p>

<p>Этим мы и&nbsp;воспользуемся во&nbsp;время решения задачи.</p>

<h2>Решение</h2>

<p>Сначала узнаем длину флага, для&nbsp;этого отправляем на&nbsp;сервер 0. Получаем в&nbsp;ответ значение<code>3f45e266104a0af716e403ee251aa94e7a55b6b6959613d98c9b0976d44818a6</code> длиной 32&nbsp;байта.</p>

<p><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/778/4c3/18a/7784c318a76d02f80fc2b2ea752b7dac.png" alt="7784c318a76d02f80fc2b2ea752b7dac.png" /></p>

<p> Мы отправили всего один байт, значит длина флага должна быть в&nbsp;пределах 16–31&nbsp;байт: если бы флаг был короче 16&nbsp;байт, то&nbsp;добавление одного байта не&nbsp;потребовало бы шифрования двух блоков текста, а&nbsp;если бы длина была больше 31, то&nbsp;добавление одного байта потребовало бы шифрования уже трёх блоков, т.е. мы бы получили не&nbsp;32, а&nbsp;48&nbsp;байт.</p>

<p>С&nbsp;этим справились, а&nbsp;теперь обратим внимание на&nbsp;первый блок текста. Из&nbsp;зашифрованного в&nbsp;первом блоке текста мы знаем один байт&nbsp;— остальные 15 нам неизвестны. А&nbsp;что&nbsp;если мы поступим наоборот&nbsp;— отправим 15 известных байт? Тогда только один байт будет нам неизвестен. Вот как&nbsp;это будет выглядеть: </p>

<p><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/5a0/ddb/696/5a0ddb6969502fc1e0ce09ddb4fa44ed.png" alt="5a0ddb6969502fc1e0ce09ddb4fa44ed.png" /></p>

<p>А&nbsp;теперь вспоминаем, что&nbsp;одинаковые открытые тексты дают одинаковые шифротексты. Это значит, что&nbsp;мы можем просто перебирать все 256 вариантов для&nbsp;этого последнего байта, каждый из&nbsp;вариантов отправить на&nbsp;сервер и&nbsp;сравнить с&nbsp;тем шифротекстом, который получили когда отправляли только 15&nbsp;байт. Как&nbsp;только шифротексты совпадут это будет значить, что&nbsp;мы угадали значение для&nbsp;этого байта.</p>

<p>То&nbsp;есть что&nbsp;мы делаем: </p>

<ol><li><p>Отправляем 15 любых байтов на&nbsp;сервер (важно только чтобы они были для&nbsp;нас известны)</p></li><li><p>Получаем от&nbsp;сервера зашифрованный текст, пусть это будет <img alt="C_x" src="https://habrastorage.org/getpro/habr/upload_files/43b/562/94e/43b56294ec804ea46a5c90a34f9d7564.svg" />. В&nbsp;этом шифротексте первый блок состоит из&nbsp;15 известных нам байтов + один байт <img alt="x" src="https://habrastorage.org/getpro/habr/upload_files/0ad/226/828/0ad226828ccc8ff78431de59fafb269a.svg" />, который мы не&nbsp;знаем.</p></li><li><p>Теперь отправляем на&nbsp;сервер 16&nbsp;байтов, где первые 15&nbsp;байтов равны байтам из&nbsp;п. 1, а&nbsp;16й перебираем (0, 1, 2, …, 255), получая при&nbsp;этом <img alt="C_0, C_1, C_2,...,C_{255}" src="https://habrastorage.org/getpro/habr/upload_files/1b9/528/dac/1b9528dac91f422835bf82eddf9ac96b.svg" /></p></li><li><p>Когда какой-то&nbsp;из&nbsp;ответов от&nbsp;сервера <img alt="C_y" src="https://habrastorage.org/getpro/habr/upload_files/c0f/c36/030/c0fc36030efe7c4a5034203db4d588ba.svg" /> совпадёт с&nbsp;<img alt="C_x" src="https://habrastorage.org/getpro/habr/upload_files/4f2/727/213/4f2727213929f71a6ab9efbd896f1490.svg" />, это будет значить, что&nbsp;<img alt="y=x" src="https://habrastorage.org/getpro/habr/upload_files/d20/e25/3f3/d20e253f3d559110ba576f35765491c0.svg" />, то&nbsp;есть мы расшифровали первый байт флага.</p></li><li><p>Теперь давайте расшифровывать второй байт. Для&nbsp;этого отправляем на&nbsp;сервер уже 14&nbsp;байтов текста. таким образом в&nbsp;первый блок шифротекста попадут уже 2&nbsp;байта из&nbsp;флага.</p><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/40e/f6d/51c/40ef6d51c53b71187a131e9825006e37.png" alt="40ef6d51c53b71187a131e9825006e37.png" /></li><li><p>Но&nbsp;ведь первый байт мы уже знаем! Значит можем проделывать пункт 3, но&nbsp;теперь отправляем 14&nbsp;байт нулей, на&nbsp;15й байт ставим первый байт флага, который мы узнали в&nbsp;пункте 4, а&nbsp;16й байт так же перебираем.</p></li><li><p>Снова, когда ответ сервера совпадёт с&nbsp;тем, что&nbsp;мы получили в&nbsp;пункте 5, мы расшифровали уже второй байт флага.</p></li><li><p>Последовательно повторяя эту процедуру мы можем найти весь флаг.</p></li></ol>

<p>Но&nbsp;флаг может быть и&nbsp;больше 16&nbsp;байт, так что&nbsp;одного блока текста нам не&nbsp;хватит, но&nbsp;это не&nbsp;проблема&nbsp;— просто добавляем ещё&nbsp;16&nbsp;байт, но&nbsp;делаем всё то&nbsp;же самое&nbsp;— всегда отправляем такой текст, чтобы в&nbsp;конце блока оставался только один неизвестный нам байт: </p>

<p><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/2b2/996/506/2b299650694d57eee75d5f2de8c5be77.png" alt="" /></p>

<p><strong>Сколько нам будет стоить эта атака? </strong> Так как&nbsp;флаг может быть длиной до&nbsp;31&nbsp;байта, то&nbsp;в&nbsp;худшем случае нам придётся перебрать <img alt="256 * 31 = 7936" src="https://habrastorage.org/getpro/habr/upload_files/0ce/e00/f86/0cee00f868956ea5a130486416b77b2e.svg" />значений. Вручную отправлять все эти запросы я, конечно, не&nbsp;хочу, поэтому я&nbsp;написал скрипт для&nbsp;решения. </p>

<pre><code class="python">import requests
import json
import curses


def main(stdscr):

    curses.resize_term(100, 300)
    stdscr.refresh()
    curses.start_color()
    curses.curs_set(0)

    curses.init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK)
    curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK)
    curses.init_pair(3, curses.COLOR_CYAN, curses.COLOR_BLACK)

    stdscr.clear()

    stdscr.addstr(0, 0, "***********************{ AES ECB CHOSEN PLAINTEXT ATTACK }***********************", curses.color_pair(2))

    # Базовый адрес для отправки запросов
    base = "http://aes.cryptohack.org/ecb_oracle/encrypt/"

    stdscr.addstr(1, 0, "Address to attack: " + base, curses.color_pair(3))
    stdscr.addstr(3, 0, "[*] Getting ciphertexts...", curses.color_pair(1))
    stdscr.refresh()

    # Собираем шифротексты до начала перебора
    block1 = []
    block2 = []

    for i in range(16, 32):
        pad = "00" * i
        response = requests.get(base + pad)
        ciphertext = json.loads(response.content)["ciphertext"]
        block1.append(ciphertext[32:64])
        block2.append(ciphertext[64:96])

    samples = block1 + block2

    stdscr.addstr(4, 0, "[!] Retrieved ciphertexts:", curses.color_pair(2))
    stdscr.addstr(5, 5, "Block 1:")

    for i in range(16):
        stdscr.addstr(6 + i, 10, samples[i])

    stdscr.addstr(7 + i, 5, "Block 2:")
    for i in range(16, 32):
        stdscr.addstr(7 + i, 10, samples[i])

    stdscr.addstr(40, 0, "[-] Attacking...", curses.color_pair(1))
    stdscr.refresh()

    # Изначально известный текст пустой
    known = ""
    t = ["-", "\\", "|", "/", "-", "\\", "|", "/"]
    for i in range(1, 33):
        stdscr.addstr(41, 0, "Known text -> " + bytes.fromhex(known).decode(), curses.color_pair(3))

        if i <= 16:
            rm = samples[-16 - i]  # Берём шифротекст из первого блока
            stdscr.addstr(6 + 16 - i, 10, rm + "<- attack", curses.color_pair(1))
        else:
            rm = samples[16 - i]  # Берём шифротекст из второго блока
            stdscr.addstr(7 + 48 - i, 10, rm + "<- attack", curses.color_pair(1))

        # Перебираем не все 256 значений, а только печатные символы, таким образом снижая количество возможных запросов с 8192 до 3040.
        for j in range(32, 127):
            stdscr.addstr(40, 0, f"[{t[j % len(t)]}] Attacking...", curses.color_pair(1))

            # Подготавливаем payload
            pad = "00" * (31 - len(known) // 2)

            # Формируем запрос
            request = base + pad + known + format(j, "02x")

            # Получаем ответ
            response = requests.get(request)
            ciphertext = json.loads(response.content)["ciphertext"][32:64]

            stdscr.addstr(42, 0, "Payload: 0x" + pad, curses.color_pair(3))
            stdscr.addstr(42, 11 + len(pad), known, curses.color_pair(2))
            stdscr.addstr(42, 11 + len(pad) + len(known), format(j, "02x"), curses.color_pair(1))
            stdscr.addstr(43, 0, "Current ciphertext: " + ciphertext, curses.color_pair(3))
            stdscr.refresh()

            # Если шифротексты совпали, то переходим к следующему шагу
            if ciphertext == rm:
                known += hex(j)[2:]

                if i <= 16:
                    stdscr.addstr(6 + 16 - i, 10, rm + "<- processed", curses.color_pair(2))
                else:
                    stdscr.addstr(7 + 48 - i, 10, rm + "<- processed", curses.color_pair(2))

                # Когда нашли закрывающую скобку, останавливаем перебор, потому что нам уже известен флаг
                if chr(j) == "}":
                    stdscr.addstr(44, 0, "[2] Attack successful!", curses.color_pair(1))
                    stdscr.addstr(45, 0, "[2] Flag retrieved: " + bytes.fromhex(known).decode(), curses.color_pair(2))
                    stdscr.getkey()
                    return
                break
    stdscr.getkey()


curses.wrapper(main)
</code></pre>

<p>Большую часть скрипта занимает код для&nbsp;красивого вывода во&nbsp;время перебора, читатели могут попытаться (и&nbsp;я&nbsp;это очень рекомендую, если хотите полноценно разобраться в&nbsp;атаке) самостоятельно написать решение без&nbsp;лишнего кода. Но&nbsp;запустить мой скрипт тоже может быть полезно, чтобы наглядно посмотреть как&nbsp;происходит расшифровывание флага.</p>

<p><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/0c2/313/31f/0c231331f3e9599a9386aaa01e5dbc9b.png" alt="0c231331f3e9599a9386aaa01e5dbc9b.png" /></p>

<h2>Заключение</h2>

<p>Вот и&nbsp;всё, такой вот он ECB. Спасибо всем, кто дочитал до&nbsp;конца, приходите в&nbsp;комментарии с&nbsp;вопросами и&nbsp;замечаниями и&nbsp;stay tuned for more;)</p>
    
            <p class="copyrights"><span class="source">&copy;&nbsp;<a target="_blank" rel="nofollow" href="https://habr.com/ru/articles/855906/?utm_source=habrahabr&amp;amp;utm_medium=rss&amp;amp;utm_campaign=855906">Habrahabr.ru</a></span></p>
                    </div>
                                                    
            <br>
            <!--<div align="left">
                <script type="text/topadvert">
                load_event: page_load
                feed_id: 12105
                pattern_id: 8187
                tech_model:
                </script><script type="text/javascript" charset="utf-8" defer="defer" async="async" src="//loader.topadvert.ru/load.js"></script>
            </div>
            <br>-->

            <div style="padding-left: 20px;">
                <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-2514821055276660"
                        crossorigin="anonymous"></script>
                <!-- PCNews 336x280 -->
                <ins class="adsbygoogle"
                     style="display:block"
                     data-ad-client="ca-pub-2514821055276660"
                     data-ad-slot="1200562049"
                     data-ad-format="auto"></ins>
                <script>
                    (adsbygoogle = window.adsbygoogle || []).push({});
                </script>
            </div>
            <!-- comments -->
                            <noindex>
                    <div style="margin: 25px;" id="disqus_thread"></div>
                    <script type="text/javascript">
                        var disqus_shortname = 'pcnewsru';
                        var disqus_identifier = '1539183';
                        var disqus_title = 'CryptoHack. Решение ECB Oracle';
                        var disqus_url = 'http://pcnews.ru/blogs/cryptohack_resenie_ecb_oracle-1539183.html';

                        (function() {
                            var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
                            dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
                            (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
                        })();
                    </script>
                    <!--<noscript>Please enable JavaScript to view the <a rel="nofollow" href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>-->
                    <!--<a href="http://disqus.com" rel="nofollow" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>-->
                </noindex>
            
        </div>

        <br class="clearer"/>
    </div>
    <br class="clearer"/>

    

        <div id="footer-2nd"></div>

        <div id="footer">
            <br/><br/>
            <ul class="horz-menu">
                <li class="about"><a href="/info/about.html" title="О проекте">О
                        проекте</a></li>
                <li class="additional-menu"><a href="/archive.html" title="Архив материалов">Архив</a>
                </li>
                <li class="additional-menu"><a href="/info/reklama.html"
                                               title="Реклама" class="menu-item"><strong>Реклама</strong></a>
                    <a href="/info/partners.html" title="Партнёры"
                       class="menu-item">Партнёры</a>
                    <a href="/info/legal.html" title="Правовая информация"
                       class="menu-item">Правовая информация</a>
                    <a href="/info/contacts.html" title="Контакты"
                       class="menu-item">Контакты</a>
                    <a href="/feedback.html" title="Обратная связь" class="menu-item">Обратная
                        связь</a></li>
                <li class="email"><a href="mailto:pcnews@pcnews.ru" title="Пишите нам на pcnews@pcnews.ru"><img
                                src="/media/i/email.gif" alt="e-mail"/></a></li>
                <li style="visibility: hidden">
                    <noindex>
                        <!-- Rating@Mail.ru counter -->
                        <script type="text/javascript">
                            var _tmr = window._tmr || (window._tmr = []);
                            _tmr.push({id: "93125", type: "pageView", start: (new Date()).getTime()});
                            (function (d, w, id) {
                                if (d.getElementById(id)) return;
                                var ts = d.createElement("script");
                                ts.type = "text/javascript";
                                ts.async = true;
                                ts.id = id;
                                ts.src = (d.location.protocol == "https:" ? "https:" : "http:") + "//top-fwz1.mail.ru/js/code.js";
                                var f = function () {
                                    var s = d.getElementsByTagName("script")[0];
                                    s.parentNode.insertBefore(ts, s);
                                };
                                if (w.opera == "[object Opera]") {
                                    d.addEventListener("DOMContentLoaded", f, false);
                                } else {
                                    f();
                                }
                            })(document, window, "topmailru-code");
                        </script>
                        <noscript>
                            <div style="position:absolute;left:-10000px;">
                                <img src="//top-fwz1.mail.ru/counter?id=93125;js=na" style="border:0;" height="1"
                                     width="1" alt="Рейтинг@Mail.ru"/>
                            </div>
                        </noscript>
                        <!-- //Rating@Mail.ru counter -->

                    </noindex>
                </li>
            </ul>
        </div>

        <!--[if lte IE 7]>
        <iframe id="popup-iframe" frameborder="0" scrolling="no"></iframe>
        <![endif]-->
        <!--<div id="robot-image"><img class="rbimg" src="i/robot-img.png" alt="" width="182" height="305" /></div>-->
        <!--[if IE 6]>
        <script>DD_belatedPNG.fix('#robot-image, .rbimg');</script><![endif]-->

    </div>

<!--[if lte IE 7]>
<iframe id="ie-popup-iframe" frameborder="0" scrolling="no"></iframe>
<![endif]-->


    <div id="footer-adlinks"></div>

    
    
    
        <noindex>


            <!--LiveInternet counter--><script type="text/javascript">
                document.write("<a rel='nofollow' href='//www.liveinternet.ru/click' "+
                    "target=_blank><img src='//counter.yadro.ru/hit?t45.6;r"+
                    escape(document.referrer)+((typeof(screen)=="undefined")?"":
                        ";s"+screen.width+"*"+screen.height+"*"+(screen.colorDepth?
                            screen.colorDepth:screen.pixelDepth))+";u"+escape(document.URL)+
                    ";"+Math.random()+
                    "' alt='' title='LiveInternet' "+
                    "border='0' width='1' height='1'><\/a>")
            </script><!--/LiveInternet-->

            <!-- Rating@Mail.ru counter -->
            <script type="text/javascript">
                var _tmr = window._tmr || (window._tmr = []);
                _tmr.push({id: "93125", type: "pageView", start: (new Date()).getTime()});
                (function (d, w, id) {
                    if (d.getElementById(id)) return;
                    var ts = d.createElement("script"); ts.type = "text/javascript"; ts.async = true; ts.id = id;
                    ts.src = "https://top-fwz1.mail.ru/js/code.js";
                    var f = function () {var s = d.getElementsByTagName("script")[0]; s.parentNode.insertBefore(ts, s);};
                    if (w.opera == "[object Opera]") { d.addEventListener("DOMContentLoaded", f, false); } else { f(); }
                })(document, window, "topmailru-code");
            </script><noscript><div>
                    <img src="https://top-fwz1.mail.ru/counter?id=93125;js=na" style="border:0;position:absolute;left:-9999px;" alt="Top.Mail.Ru" />
                </div></noscript>
            <!-- //Rating@Mail.ru counter -->



            <!-- Yandex.Metrika counter -->
            <script type="text/javascript">
                (function (d, w, c) {
                    (w[c] = w[c] || []).push(function () {
                        try {
                            w.yaCounter23235610 = new Ya.Metrika({
                                id: 23235610,
                                clickmap: true,
                                trackLinks: true,
                                accurateTrackBounce: true,
                                webvisor: true,
                                trackHash: true
                            });
                        } catch (e) {
                        }
                    });

                    var n = d.getElementsByTagName("script")[0],
                        s = d.createElement("script"),
                        f = function () {
                            n.parentNode.insertBefore(s, n);
                        };
                    s.type = "text/javascript";
                    s.async = true;
                    s.src = "https://mc.yandex.ru/metrika/watch.js";

                    if (w.opera == "[object Opera]") {
                        d.addEventListener("DOMContentLoaded", f, false);
                    } else {
                        f();
                    }
                })(document, window, "yandex_metrika_callbacks");
            </script>
            <noscript>
                <div><img src="https://mc.yandex.ru/watch/23235610" style="position:absolute; left:-9999px;" alt=""/>
                </div>
            </noscript>
            <!-- /Yandex.Metrika counter -->

            <!-- Default Statcounter code for PCNews.ru http://pcnews.ru-->
            <script type="text/javascript">
                var sc_project=9446204;
                var sc_invisible=1;
                var sc_security="14d6509a";
            </script>
            <script type="text/javascript"
                    src="https://www.statcounter.com/counter/counter.js"
                    async></script>
            <!-- End of Statcounter Code -->

            <script>
                (function (i, s, o, g, r, a, m) {
                    i['GoogleAnalyticsObject'] = r;
                    i[r] = i[r] || function () {
                            (i[r].q = i[r].q || []).push(arguments)
                        }, i[r].l = 1 * new Date();
                    a = s.createElement(o),
                        m = s.getElementsByTagName(o)[0];
                    a.async = 1;
                    a.src = g;
                    m.parentNode.insertBefore(a, m)
                })(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');

                ga('create', 'UA-46280051-1', 'pcnews.ru');
                ga('send', 'pageview');

            </script>

            <script async="async" src="/assets/uptolike.js?pid=49295"></script>

        </noindex>
    



<!--<div id="AdwolfBanner40x200_842695" ></div>-->
<!--AdWolf Asynchronous Code Start -->

<script type="text/javascript" src="https://pcnews.ru/js/blockAdblock.js"></script>

<script type="text/javascript" src="/assets/jquery.min.js"></script>
<script type="text/javascript" src="/assets/a70a9c7f/jquery/jquery.json.js"></script>
<script type="text/javascript" src="/assets/a70a9c7f/jquery/jquery.form.js"></script>
<script type="text/javascript" src="/assets/a70a9c7f/jquery/jquery.easing.1.2.js"></script>
<script type="text/javascript" src="/assets/a70a9c7f/jquery/effects.core.js"></script>
<script type="text/javascript" src="/assets/a70a9c7f/js/browser-sniff.js"></script>
<script type="text/javascript" src="/assets/a70a9c7f/js/scripts.js"></script>
<script type="text/javascript" src="/assets/a70a9c7f/js/pcnews-utils.js"></script>
<script type="text/javascript" src="/assets/a70a9c7f/js/pcnews-auth.js"></script>
<script type="text/javascript" src="/assets/a70a9c7f/js/pcnews-fiximg.js"></script>
<script type="text/javascript" src="/assets/a70a9c7f/js/pcnews-infobox.js"></script>
</body>
</html>