Огляд захистів програмного забезпечення під Linux

Red Plait

У цій статті я спробував зробити огляд Linuxа з точки зору досвідченого крякери,
для якого немає нічого святого :-). OpenSource, без сумніву, штука хороша
і корисна. Але – чомусь постійно хочеться їсти, в тому числі і програмістам
і навіть жителям Villabajo (як Ви думаєте, чому у них постійно брудний посуд?).
Уявімо, що Ви витратили купу часу і ресурсів,
розробляючи останні кілька місяців непогану програму, і навіть чомусь маєте
версію для Linuxа. Мабуть, Ви хочете окупити витрачені зусилля – простіше кажучи,
заробити на своїй програмі купу грошей. І якщо Ви не опублікували свій вихідний
код, в надії заробити іншими способами (до речі, розповіли б тоді, якими
саме), то необхідно якось захистити Вашу програму від

  1. несанкціонованого копіювання. Тобто програма повинна працювати тільки у того,
    хто її купив, і не повинна працювати (або працювати неправильно, або, що буває
    частіше за все, мати функціональні обмеження) у халявщиків і крякери.

  2. плагіату. Або reverse engeneeringа. Простіше кажучи, щоб Ваш унікальний
    алгоритм не змогли поцупити

Якщо розробці / зняття захистів під DOS / Windows присвячено безліч сайтів (включаючи
пріснопам'ятну Fravia), то я особисто не бачив жодної роботи, присвяченої того ж
самому, але під Linux. Тим часом вже є безліч комерційних програм під
цю, загалом, не найгіршу з поширених OS. З великим сумом мушу зазначити,
що вся їх "захист" становить максимум 1% від їх Windows аналогів,
і знімається в гіршому випадку за пару годин. Я постараюся пояснити, чому відбувається саме
так, чому під Linuxом не працюють багато традиційно використовуються для захисту
Win32 програм способи, і можливо після прочитання цього шедевра творчості
душевнохворих Ви навіть зможете придумати щось дійсно ефективне.
"А хто такий автор, щоб судити про настільки високі матерії?" – Цілком
справедливо може запитати читач. А ніхто, по суті. Так, зламав пару
десятків програм і вирішив зробити що-нть корисне. Див. епіграф, одне слово …

Отже, в чому ж головна відмінність Linux від інших OS? У доступності вихідного коду
Найбільше (ну, крім хіба що Вашої програми, що стоїть мегабакси), що працює (або не працює)
на Вашій машині. Це робить написання захисту під Linux просто кошмаром – Ви не
можете бути впевнені, що функція strcmp зі стандартної C run-time library –
це дійсно strcmp, а не її змінений (зазвичай не на Вашу користь) емулятор.
Ви не можете довіряти нічому у такій операційній системі – внаслідок доступності
вихідного коду будь-яка її частина може бути модифікована крякери для злому
Вашої програми, включаючи такі найважливіші компоненти, як ядро і run-time library.
Ваша програма працює в самій агресивному середовищі, яку тільки можна собі уявити.
У самому справі, якщо б Ви могли змінити в Windows 9x, скажімо, kernel32.dll (я маю
на увазі не копирсання в машинному коді за допомогою дизассемблера, хоча + tsehp використовував
і такий метод – ні, просто редагування вихідного коду і подальша перекомпіляція
– Набагато ефективніше, чи не так?) – Хіба було б можливе існування
захистів начебто VBox? Я настійно рекомендую добре подумати про це як-небудь
на дозвіллі.
А поки у мене для Вас погані новини – Ваша програма обов'язково буде зламана.
Це насправді "чиста" економічне питання. Уявімо, що Ваш програмний продукт
коштує 1000 $. Середньомісячна зарплата непоганого програміста з нашої сраний
навряд чи становить 200 $. Таким чином, якщо який-небудь хлопчина з Сибіру
витратить на злам Вашого творіння менше 5 місяців – він буде економічно вигідний.
Зауважте, що тут ні слова не було сказано ні про операційну систему, під
якої працює Ваш шедевр, ні про складність і вартості використаної системи
захисту.
Що ж робити – йти в монастир (хоча, в жіночий іноді навідуватися –
напевно, непогана ідея :-)? Ви повинні розглядати захист свого програмного
продукту не як 100% засіб від Ваших головних болів, а всього лише як засіб,
затрудняющее життя крякери. Скажімо, якщо Ваш захист зупинить 9 крякери з 10
– Це дуже непоганий результат. Звичайно, не всі 9 зупинених куплять Вашу
програму, але їх буде явно більше, ніж для випадку, коли Ваша захист зламаний 9тью
з 10. Втім, можливо, що я жорстко не правий – я особисто ніколи не купував програмних
продуктів 🙂
Отже, досить пустих розмов. Для початку я зроблю короткий огляд застосовуваних
крякери інструментів (хоча я схильний розглядати Linux як один великий
інструмент крякери).

Відладчики

Дивно, але до цих пір ніхто не переписав SoftIce під Linux – я особисто не бачу
цього жодних перешкод. Більше того, у мене є вагомі докази, що
це не просто можливо зробити, але і що його реалізація буде значно легше
і простіше, ніж під всякі глючний операційні системи від не-будемо-показувати-пальцем-кого.
Так що поки що з відладчиками набагато гірше (для крякери,
для авторів захистів – навпаки, хоча адже регламентуватиме все одно якось потрібно),
ніж під Win32. Отже, що є / придатно до вживання (див. також розділ Debuggers
на LinuxLinks):

Дизасемблери

Без сумніву, IDA Pro. Також іноді можна на швидку руку використовувати Biew, objdump (або навіть ndisasm,
дізассемблер від Netwide Assembler), але це несерйозно. Мені більше невідомі
інструменти, які дозволяють дописувати до них нові процесорні модулі,
завантажувачі для нестандартних форматів файлів, а також plugins, що полегшують
автоматичний / інтерактивний аналіз. До того ж Ільфак погрожував випустити
якраз версію під Linux (як іронічно :-). Хіт третього сезону поспіль, в загальному.
Гаразд, на цей раз обійдуся без звичайних наїздів :-).

strace

або truss під UnixWare. Аналог regmon / filemon / BoundsChecker в одному флаконі.
Ядро Linuxа має підтримку перехоплення системних викликів (функція ptrace).
Тобто можна запустити будь-який процес як відповідальний трасуванні через ptrace,
і Ви зможете відстежити всі системні виклики з їх параметрами. Більше того,
після невеликої модифікації ця функція (яка має доступ до віртуальної
пам'яті трассируемого процесу) може бути використана, наприклад,
для run-time patching, впровадження коду в адресний простір будь-якого процесу,
і так далі, і тому подібне.
Нехай кине в мене камінь після всього описаного вважає, що Linux написали не
крякери :-). Якщо ж серйозно, то я поки не зміг вигадати способу, як на
userlevel протидіяти цій кувалди. Більше того, я поки що навіть не можу
дізнатися, що мій процес зараз трасує ядром! Втім, пару
рішень можна придумати:

  1. можна написати свій модуль ядра, внаслідок монолітної структури Linux kernelа
    він буде мати доступ до всіх структур ядра, в тому числі і до відповідають за
    ptrace

  2. Менш надійне, але більш просте в реалізації засіб. Можна викликати ptrace
    для самого себе. Якщо виклик був невдалий – значить, нас вже трасує, потрібно
    зробити що-нть з цього приводу (sys_exit, наприклад, а не те, що Ви подумали :-).
    Втім, адже якщо нас вже трасує, нічого не варто перехопити даний виклик
    і повернути все що завгодно …

Memory dumpers

Ну, тут навіть і писати нічого потрібно. Файлова система / proc може використовуватися
для таких цілей. Файл maps використовується як карта виділеної процесу віртуальної
пам'яті, а файл mem є її відображенням, можна зробити в ньому seek на
потрібну адресу та легко зберігати необхідну ділянку в файл або куди-Ви-там-хочете.
Проста програма на Perlе розміром 30 рядків може бути використана для
зняття дампа пам'яті Вашої дорогоцінної програми та збереження її у файл. Набагато
простіше, ніж під Win32 🙂

Шістнадцяткові редактори

З цим теж ніколи не було великих проблем. Навіть стандартний просмотровщик
Midnight (або Mortal його друге ім'я) Comanderа вміє редагувати в шістнадцятковому
поданні. Для гурманів рекомендую Biew.

Стандартні засоби розробки

Це не жарт. Для модифікації ядра / runtime бібліотек (а також для створення
patchей і keygenов) потрібен як мінімум компілятор "C". Втім,
я як-то пару раз писав keygenи на Perlе :-).

Способи захисту

Отже, я сподіваюся, Ви ще не заснули і впали в депресію. Що ж можна протиставити
всьому вищеописаному? Явно годяться далеко не всі методи, використовувані в Win32.
Тим не менш, я зміг придумати декілька, і я впевнений, що це далеко не всі …

Проти BFD (GDB, objdump, strings etc)

BFD – це бібліотека для маніпуляцій з бінарними файлами. За щасливим збігом
обставин вона використовується також відладчиком GDB. Але для ELF формату
(Найбільш поширений формат здійснимих файлів під усіма сучасними Unixамі)
вона реалізує неправильний алгоритм завантаження (подробиці див у моїй статті
"Як зробити Linux програми менше"). Для цього можна використовувати
sstrip чи мій ELF.compact (Власне, вони роблять одне і те ж,
просто коли я писав свій tool, я не знав про існування sstrip).
Переваги:

на цьому переваги закінчуються і починаються Суцільні недоліки:

Даний варіант може розглядатися як дуже простий захист від повних
ламерів, rating 0.1%.

Компресія / шифрування коду програми

Найпоширеніший метод захисту під Win32. Під Linuxом цей метод має
деякі особливості. Найбільша відмінність у тому, як вирішуються посилання
на зовнішні модулі.

Статична лінкування

Не має аналогів під Win32 метод. При складанні програми всі використовувані
нею бібліотеки просто статично лінкуются в один великий товстий модуль. Тобто
для запуску такої програми нічого додатково робити не потрібно – така програма
самодостатня. Переваги:

Недоліки:

Rating: 0.1%.
Також можливо, що хтось нть стане кидати в мене цеглини, стверджуючи, що статична лінкування
де збільшує необхідні ресурси, і що нібито Спільні бібліотеки поділяють
сегмент коду між всіма процесами, які використовують їх. Я теж так думав, більше
того, те ж саме стверджують більшість бачених мною підручників з Unix, але це не так.
Найпростіше доказ – подивіться атрибути сегментів пам'яті, займаних
розділяються бібліотеками (файл maps у файловій системі /proc). Ви
майже ніколи не побачите атрибуту s(Hared). Чому? Коротка відповідь звучить
так – через ELF. Справа в тому, що при завантаженні ELF файлу відбувається настроювання
його переміщуються адрес – relocations. При цьому сегменту пам'яті (навіть якщо
це сегмент коду) присвоюються атрибути Read / Write, і якщо він при цьому поділявся
декількома процесами, відбувається копіювання пам'яті при записі. Таким чином,
поділ сегментів коду між процесами можна тільки між батьком і його
нащадками (як результат функції fork). За подробицями звертайтеся до исходниками
kernalа. До речі, ці ж аргументи застосовні і до твердження, що нібито "упаковка коду
програми призводить до збільшення ресурсів, необхідних для запуску такої програми ".
Як бачите, панове-слюніксоіди, як мінімум в управлінні віртуальною пам'яттю
Linux нічим не краще виробів від M $
Декомпресора / декріптор для статично зв'язані файлів нічим не відрізняються
від своїх DOSових предків, власне, тут навіть і говорити нема про що. Я в справжній
момент зайнятий написанням такого звіра – просто додається код розпакування і змінюється
точка входу на нього.

Динамічна лінкування

Під Win32 всі програми є динамічно зв'язані як мінімум з
системними. DLL, що реалізовують API. Більш того, така лінкування здійснюється
знову ж самою системою за допомогою все тих же функцій API. Під Linuxом все по-іншому.
По-перше, програму зовсім необов'язково бути динамічно зв'язані. По-друге,
програма сама повинна піклуватися про завантаження всіх необхідних модулів і динамічному
розв'язанні посилань. Це, правда, зовсім не означає, що Вам щоразу доведеться
писати код завантаження модуля в пам'ять. Середовище виконання (а не kernal – як у Win32)
надає реалізацію динамічного завантажувача за умовчанням – в термінах Linux
його називають ELF interpretor, Або просто interpretor (Я не знаю точної
перекладу і взагалі сумніваюся в його наявності, так що буду просто використовувати
оригінальний термін). При лінковке в програму можна відслідковувати, що для її
запуску необхідно після завантаження самої програми також завантажити interpretor,
і передати йому управління. На цьому завантаження файлу на виконання з точки зору
Кернела закінчується. Але Ваші головні болі тільки починаються! Отже, чим поганий
стандартний ELF interpretor?

  1. Тим же, чим і весь Linux – його вихідні коди доступні, відповідно, він може
    бути легко змінений для досягнення невідомих вам цілей

  2. Він підтримує унікальний для Unixов механізм попереднього завантаження
    (Я особисто не знаю аналогів під Win32). Розглянемо його докладніше. Отже, припустимо,
    що Ваша програма імпортує функцію strcmp. Злісний крякери може написати
    власну реалізацію цієї функції, створити об'єктний модуль і використовувати її
    замість тієї, що Ви чекали! Для цього всього лише потрібно визначити змінну
    середовища LD_PRELOAD, Щоб вона містила перелік модулів, що підлягають
    завантаженні ПЕРЕД імпортованими Вашою програмою. Логіка роботи interpretorа
    така, що якщо якась імпортована функція вже дозволена, то вона більше не
    дозволяється (в Linux імпорт відбувається на ім'я, а не по імені та імені бібліотеки,
    як в Win32). Таким чином, можна штатними засобами впровадити в адресний
    простір Вашої програми все що завгодно, замінивши при цьому будь-яку імпортовану
    функцію. Схоже на нічний кошмар, чи не так? Ви все ще думаєте, що Linux написали не крякери :-)?

  3. З стандартним interpretorму є й ще одна проблема. Справа в тому,
    що його легко можна переписати для універсального інструмента, що відслідковує
    всі виклики імпортованих функцій. У мене була ідея вмонтувати script engine в
    ELF interpretor, Так що більше не потрібно буде переписувати його під
    кожну конкретну програму, а всього лише замінити script, який і зробить
    все що потрібно. Адже ELF interpretor працює в адресному просторі
    Вашої програми, і він відповідає за початкове завантаження імпортованих
    функцій, тобто фактично такий script буде мати над Вашою програмою повний контроль
    (Під Win32 мені взагалі невідомі подібні інструменти. BoundsChecker звичайно
    може відстежувати всі виклики імпортованих функцій, але поки нікому не прийшла
    думка дописати до нього script engine і використовувати, наприклад, для memory patching).
    Поки Ви можете зітхнути вільно – через нестачу часу ця ідея вже третій
    місяць лежить у довгому ящику. Але ж якщо вона прийшла в мій збочений мозок – вона
    може відвідати і кого-нть менш зайнятого 🙂

Якщо після прочитання попереднього абзацу Ви все ще не викинулися з вікна – у мене
Тобто для Вас і хороші думки. Отже, що можуть протиставити автори захисту:

  1. Власний завантажувач у Кернел
    Можна переписати стандартний завантажувач ELF файлів (файл binfmt_elf.c з
    директорії fs исходников ядра Linux), щоб зробити життя крякери кілька
    важче. Наприклад, скидати прапор трасування процесу, мати власний формат
    здійснимих файлів (природно разом з перекодувальника звичайних ELFов в цей
    формат), декріптовать / декомпрессіровать шматки файлу перед завантаженням їх у пам'ять –
    у мене досить багата фантазія. Недоліки:

    • як не дивно, найбільшим недоліком є необхідність мати
      власний код в kernelе. Оскільки у кінцевого користувача час від часу
      виникає необхідність у перезібравши Кернела, Ви повинні будете надати або
      об'єктний модуль (для насмерть прибитого цвяхами в Кернел коду) або знову об'єктний
      модуль (для LKM – Linux Kernal Module). І те й інше легко можна
      дизасемблювати / пропатчити і історія повторюється …
    • Як щодо зміни версії Кернела? Наприклад, гряде зміна ядра 2.2 на 2.4 –
      потрібно буде мати (і підтримувати!) як мінімум код для обох версій ядра …
    • А якщо Ви допустите помилку? Якщо для userlevel програм Ваші помилки швидше
      за все не смертельні, то помилки в коді Кернела можуть мати вельми плачевні наслідки.
      Крім того, налагодження такого коду є сущим кошмаром, повірте мені …
    • Суб'єктивна причина – складність установки. Якщо Вам скажуть, що для використання,
      скажімо, Photoshopом Вам доведеться завантажити модуль ядра, а при збірці ядра Вам необхідно
      зробити такі-то дії – я просто хочу знати Вашу реакцію :-)…

    При всіх недоліках це цілком прийнятний варіант для серверів, що працюють в режимі 7x24x365,
    де Кернел модернізується раз на рік (при обов'язковій умові, що Ваш
    Вбудовувана у Кернел код глючить відносно рідко). Так що для серверних
    додатків (будь-нть баз даних, наприклад), real-time програм, та програм,
    все одно вимагають змін у Кернел (наприклад драйверів будь-нть замовного
    заліза, див. нижче), цей варіант цілком може використовуватися …
    Rating: 1-20%, залежно від реалізації.

  2. Власний ELF interpretor
    Тільки подумайте про те, чого можна домогтися, використовуючи власну версію ELF interpretorа!

    • Скажіть "прощай" попередньої завантаженні, підтримки отладчика
      (До речі, хіба я не казав, що для нормальної роботи все того ж GDB
      стандартний ELF interpretor повинен мати деякі функції?) і тому
      подібним принад …
    • Адже майже все, що було описано вище для завантажувача у ядрі застосовується також
      і для ELF interpretorа. Він може перед передачею управління основною
      програмі розпакувати / декріптовать її частини, перевірити наявність / відсутність ліцензії,
      перевірити наявність отладчика в пам'яті і багато ще чого. Причому, на відміну від
      LKM, він може використовувати стандартну C runtime library, його написання (і саме
      головне налагодження) набагато легше, і помилка в ньому (взагалі-то всі програми містять
      ошибки. Будь-яке тестування може виявити лише їх наявність, але ніяк не їх
      відсутність) не призведе до необхідності перезавантаження системи і псування системних
      файлів (ну, якщо Ви не працюєте як root :-).
    • Він може зняти ще одну Вашу головний біль – перевіряти справжність поділюваних
      бібліотек. Припустимо, що Ви підписуєте всі використовувані Вами бібліотеки (скажімо,
      за допомогою MD5 або SHA hash). Тепер при завантаженні такої бібліотеки можна повторно обчислити
      цифровий підпис – і якщо фактична не збігається з наявною – ба, та нас ламають!
      Тобто з деякою ймовірністю Ви будете впевнені після цієї процедури, що
      виклик нещасної функції strcmp буде викликати Вашу реалізацію strcmp з підписаної
      Вами бібліотеки …

    Мій збочений мозок може генерувати досить багато застосувань власної
    версії ELF interpretorа, Але цей спосіб також має недоліки:

    • ELF interpretor – Всього лише програма. Тому вона також може
      бути дизасемблювати / пропатчена …
    • Як щодо memory dump / memory patch? Звичайно, можна зробити так, щоб
      перед передачею управління головній програмі ELF interpretor затирав
      в пам'яті всі дані, використані для її завантаження (символьну і строкову
      таблиці, інформацію про relocations і т.д.), але тим не менше все одно Ваша
      програма в пам'яті знаходиться в бойовій готовності до виконання, повністю
      розпакованої, розшифрованої, з широко розкинутими ногами, загалом 🙂
      Цілком реально написати memory dumper, який буде формувати на диску
      статично "зв'язані" здійснимих файл прямо з образу Вашої програми
      в пам'яті. Більше того, цей файл буде працездатний (адже в пам'яті вже одного разу все
      було готове до виконання, всі бібліотеки завантажені, relocations виконані …) –
      просто размерчик у нього буде більше середнього :-).
      Нда, треба буде нть зайнятися цим …
      Також досить багатообіцяючим виглядає модифікація шматка коду в Кернел, що відповідає
      за формування core dumps.

    Rating: 1-20%, сильно залежить від реалізації.

Застосування віртуальної середовища виконання

Так, щось на зразок старої недоброї Java – Свій асемблер, свій процесор,
свою мову. Необов'язково писати всю програму на такому чудовисько – досить
написати на ньому макроси, і додати до них парочку, що відповідає за захист (якщо
я ще не вижив з розуму остаточно, є такий непоганий редактор CRiSP, Добре
попівшій у мене крові в свій час – в ньому використовувалася саме така схема захисту. Йому
правда все одно не допомогло :-). Якщо з достоїнствами все ясно, то недоліки
дещо менш очевидні:

Застосування апаратних засобів захисту

Я особисто вважаю, що якщо якась пригвинчене до порту залізяка (dongle) не несе
у собі частину функціональності самої програми, то вона просто марна, і
програма, "захищена" таким чином, легко може, і більше того,
просто зобов'язана бути зламана. Така затичка займає цілий порт,
яких і так зазвичай недостатньо (я бачив одного разу гірлянду з п'яти dongles,
нанизаних один на одного. Як казав ослик Іа, "байтораздірающее видовище" :-).
Зверніться до хлопців з UCL, вони набагато краще за мене розбираються в даному питанні.
Але ми маємо зовсім іншу картину, якщо така залізяка використовується не просто
в декоративних цілях, а реально необхідна для функціонування програми.
Приклади таких апаратно-програмних комплексів:

Тобто без такої залозки Ваша програма просто не має сенсу. Можна взагалі не піклуватися
про її захист (можливо навіть її поширення під GPL) – якщо такі залозки виробляєте
тільки Ви, і більше ніхто. Втім, завжди пам'ятайте про таку страшну штуку,
як пошарове копіювання (особливо цим пробавлялися Японія і не так давно СРСР) …

Клієнт-сервер

Як розвиток ідеї з попереднього пункту можна уявити перекладення частини функціональності
не на залізо, а на програму. Але це не продається користувачеві програма,
а встановлена у Вас – сервер, що вміє виконувати частину функціональності, запитуваної
клієнтською частиною, встановленою на комп'ютері користувача. Причому клієнтська
частина може поширюватися безкоштовно, а гроші брати саме користування сервером.
Мабуть, тоді несанкціоноване копіювання такої клієнтської програми просто не має
сенсу … Втім, це вже сфера компетенції хакерів, а не крякери 🙂

Практична реалізація

Суха теорія, та древо життя пишно зеленіє …
Майже все вищеописане має один недолік – це все плоди мого не в міру запаленого
свідомості. Так що пару тижнів тому я таки реалізував деякі з вищевикладених
ідей – я написав справжній packer / runtine unpacker для Linuxа (перший, наскільки
я знаю. Справа в тому, що UPX і ExePack не є справжніми unpackerамі,
які ми звикли бачити під DOS/Win32. Вони просто розпаковують здійснимих файл
в тимчасову директорію, запускають його як звичайний файл, а після закінчення роботи
видаляють. Свята наївність в дії …). Я маю версію як для Linux з ядром
2.2, так і під FreeBSD 3.4 (і є підозри, що він буде працювати також і з
версіями FreeBSD 3.X). Розпакування відбувається в моєму власному ELF interpretorе,
він також не підтримує GDB і LD_PRELOAD. Внаслідок цього,
поки мій packer вміє стискати тільки файли з ELF interpretorму. Справедливості
ради треба зауважити, що таких в Red Hat 6.1 близько 96%. До речі, мій packer / unpacker
також продається 🙂
Для демонстрації його можливостей я написав простий crack.me під Linux
(Знову ж перший, наскільки я знаю), упакований моїм packerом. Він вимагає
Linux з ядром 2.2 і glibc 2.1 (так так, я навмисно використовую динамічну
лінковки з C RunTime library-як найгірший варіант для застосування envelop-захисту
зразок мого packerа). Інсталяція дуже проста – помістіть файл
rp-linux.so.0 (Власне це і є мій ELF interpretor)
в директорію /lib і дайте йому права на виконання.
Ці операції необхідно виконати від користувача root. А далі запускайте
мій crack.me (Під яким хочете користувачем. Взагалі для параноїків, в
будь-якому shell scriptе що бачить трояна, я рекомендував би використовувати окремо
стоїть машинку без мережевої карти з тільки що зробленим backupом).
Сенс полягає в тому, щоб змусити його сказати що-нть
крім "You are wrong". Для цього Ви повинні ввести пароль. Пароль
має довжину більше восьми символів, що робить простий перебір кілька неефективним
(Я сподіваюся :-).

Скарги та пропозиції

(А також якщо у Вас з'явиться нав'язлива ідея найняти мене на роботу 🙂
можна відправляти автору за адресою redplait@usa.net.

#include <std_disclaim.h>

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*