Чи компілятор Кена Томпсона все ще є загрозою?


156

Кен Томпсон Хак (1984)

Кен Томпсон окреслив метод пошкодження бінарного компілятора (та іншого компільованого програмного забезпечення, як скрипт входу в системі * nix) у 1984 році. Мені було цікаво дізнатися, чи вирішила сучасна компіляція цей недолік безпеки чи ні.

Короткий опис:

Перепишіть код компілятора, щоб він містив 2 недоліки:

  • При складанні власного двійкового файлу компілятор повинен зібрати ці вади
  • При компілюванні якогось іншого попередньо вибраного коду (функція входу) він повинен скласти деякий довільний бекдор

Таким чином, компілятор працює нормально - коли він компілює сценарій входу або подібний, він може створити захисну систему, а коли в майбутньому збирає новіші версії себе, він зберігає попередні недоліки - і недоліки будуть існувати лише у компіляторі двійкові, тому їх надзвичайно важко виявити.

Запитання:

Я не зміг знайти відповіді на них в Інтернеті:

  • Як це пов’язано із складанням щойно вчасно?
  • Чи компілюються такі функції, як програма, що обробляє входи в системі * nix, під час їх запуску?
  • Це все ще є дійсною загрозою, чи відбулися розробки в безпеці компіляції з 1984 року, які заважають цьому не стати важливим питанням?
  • Це впливає на всі мови?

Чому я хочу знати?

Я натрапив на це під час виконання домашніх завдань, і це здалося цікавим, але мені не вистачає досвіду, щоб зрозуміти конкретно, чи це поточна проблема чи вирішена проблема.

Довідковий матеріал


6
Стратегія різноманітного подвійного компіляції є досить надійним способом виявлення наявності скомпонованого компілятора RoTT.
dmckee

3
Я думаю, що АНБ доклало багато роботи до подібного нападу.
Пол М

Відповіді:


110

Цей злом слід розуміти в контексті. Він був опублікований в той час і в культурі, де домінуючою системою була Unix, яка працює на всіляких різних технічних засобах.

Що зробило напад так страшно було , що C компілятор центральній частині програмного забезпечення для цих систем. Практично все в системі пройшло через компілятор при його першому встановленні (бінарні розподіли були рідкісними через гетерогенне обладнання). Усі постійно збирали речі. Люди регулярно перевіряли вихідний код (їм часто доводилося вносити корективи, щоб змусити його складати), тож маючи компілятор вводити на задній план, здавалося, це свого роду «ідеальний злочин», коли вас не могли спіймати.

На сьогодні апаратне забезпечення набагато сумісніше, тому компілятори відіграють набагато меншу роль у щоденній роботі системи. Компромісний компілятор вже не найстрашніший сценарій - руткіти та компрометований BIOS ще складніше виявити та позбутися.


27
Або оскільки більшість людей нічого не збирає з джерела (скажімо, в Windows), то середньому трояну вистачить :) (Я згоден, що компромісний компілятор є надмірним)
Андрес Ф.

16
@ArjunShankar: Невільний фірмовий бінарний компілятор не потребує та не може мати цей бекдор. Цей backdoor застосовується лише до компіляторів, які ви збираєте самостійно з вихідного коду.
ruakh

12
За винятком робочого столу, Unix та всі його варіанти все ще є домінуючою операційною системою.
Роб

7
@ruakh: можливо, я не розумію вашого акценту на "це", але я погоджуюся. Якщо цей бекдор був введений в компанії, яка має власником невільний, власний компілятор і використовує цей компілятор для компіляції нових версій того ж компілятора, цей бекдор може мати набагато гірший вплив, ніж у початковому сценарії. Вам буде потрібно лише один вектор нападу, щоб заразити всіх.
orithena

8
Уявіть, що хтось компрометує сервер збірки ubuntu і замінює компілятор, не змінюючи жодного джерела. Це може зайняти небагато часу, щоб це з'ясувати, і до цього часу зображення ubuntu будуть витіснені для людей з укомплектованим в них компромісним компілятором (разом із компрометованими входами для входу або тим, що у вас є). Я думаю, що це все ще є абсолютно вагомим питанням.
Джиммі Хоффа

74

Метою виступу не було висвітлити вразливість, яку потрібно вирішити, або навіть запропонувати теоретичну вразливість, про яку ми маємо усвідомлювати.

Мета полягала в тому, що, якщо говорити про безпеку, ми хотіли б нікому не довіряти, але, на жаль, це неможливо. Ви завжди повинні довіряти комусь (звідси назва: "Роздуми про довіру довіри")


Навіть якщо ви параноїдальний тип, який шифрує його жорсткий диск на робочому столі та відмовляється запускати будь-яке програмне забезпечення, яке ви не склали самостійно, все одно потрібно довіряти своїй операційній системі. І навіть якщо ви самостійно компілюєте операційну систему, вам все одно потрібно довіряти використовуваному компілятору. І навіть якщо ви збираєте власний компілятор, вам все одно потрібно довіряти цьому компілятору! І це навіть не згадуючи виробників обладнання!

Ви просто не можете піти з довірою нікому . Це справа, яку він намагався перетнути.


2
Якщо у вас є компілятор з відкритим кодом, поведінка якого не залежить від визначеної чи визначеної реалізацією поведінки, компілює його за допомогою різних незалежно розроблених компіляторів (довірених чи ні), а потім компілює одну програму, використовуючи всі різні компільовані версії що з відкритим кодом, кожен компілятор повинен виробляти абсолютно однаковий вихід. Якщо вони це роблять, це говорить про те, що єдиним способом, як троян може бути, був би, якби він був однаковим у всіх. Це здавалося б малоймовірним. Один з моїх пієв з великою кількістю .net, хоча, ...
supercat

9
@supercat: Ви, здається, не вистачаєте пункту. Ви говорите, що представлений хак Кен Томпсон можна обійти. Я кажу, що конкретний хак, який він вибрав, не має значення; це був лише приклад, щоб продемонструвати його більшу думку, що ви завжди повинні комусь довіряти . Ось чому це питання дещо безглузде - воно повністю пропускає ліс за деревами.
BlueRaja - Danny Pflughoeft

9
@supercat: Це малоймовірно, що різні компілятори створюватимуть один і той же байт-код для будь-якої нетривіальної програми через різні дизайнерські рішення, оптимізації тощо. Це викликає питання - як ви навіть знаєте, що бінарні файли однакові?
Анкіт Соні

1
@AnkitSoni: Моя відповідь детальніше. Подання належним чином написаного компілятора з відкритим кодом / лінкером через різні компілятори повинно отримати різні виконувані файли, які будуть вести себе однаково . Якщо виконувані файли насправді поводяться однаково, вони дадуть той самий вихід, якщо через них буде переданий код для компілятора з відкритим кодом / лінкера. Для порівняння файлів можна скопіювати їх на дискету та використати антикварний комп'ютер для їх порівняння.
supercat

2
Невже деякі з цієї розмови не означатимуть, що для тестованих речей двійкові файли / обладнання поводилися так, як очікувалося? У ньому все ще може бути щось, чого ви не тестували і про що не знаєте.
Bart Silverstrim

53

Немає

Напад, як спочатку описано, ніколи не представляв загрози. Хоча компілятор теоретично міг би це зробити, фактично зняття атаки вимагало б програмування компілятора на

  • Розпізнайте, коли вихідний код складається, є компілятором, і
  • З’ясуйте, як змінити довільний вихідний код, щоб вставити хак у нього.

Це тягне за собою з'ясування того, як працює компілятор зі свого вихідного коду, щоб він міг модифікувати його без злому.

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

Це самонаправлене програмування на високому рівні, важка проблема з AI (востаннє я перевірив, стан техніки створював код, який практично визначається його типами). Подивіться: мало хто навіть може це зробити; вам доведеться вивчити мову програмування та спершу зрозуміти базу коду.

Навіть якщо проблема AI буде вирішена, люди помітять, якщо компіляція їх крихітного компілятора приводить до двійкового файлу з величезною бібліотекою AI, пов'язаної з ним.

Аналогічна атака: завантаження довіри

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

Приклад, який легко можна зняти в реальному житті

Ваша операційна система, скажімо, Ubuntu Linux, забезпечує безпеку (цілісність) оновлень, перевіряючи завантажені пакети оновлень на ключ підпису сховища (використовуючи криптографію відкритого ключа). Але це гарантує справжність лише оновлень, якщо ви зможете довести, що ключ підпису належить законному джерелу.

Де ви взяли ключ підписання? Під час першого завантаження дистрибутиву операційної системи.

Ви повинні вірити, що джерело вашої довіри, цей ключ підпису, не є злим.

Кожен, хто зможе MITM підключитись до Інтернету між вами та сервером завантаження Ubuntu - це може бути ваш Інтернет-провайдер, уряд, який контролює доступ до Інтернету (наприклад, Китай), або постачальник хостингових послуг Ubuntu - міг би захопити цей процес:

  • Виявіть, що ви завантажуєте зображення компакт-диска Ubuntu. Це просто: дивіться, що запит переходить до будь-якого (публічно переліченого) дзеркала Ubuntu і запитує ім'я файлу образу ISO.
  • Обслуговуйте запит від власного сервера, надаючи образу CD, що містить відкритий ключ та місце сховища атакуючого, а не Ubuntu.

Відтепер ви надійно отримаєте свої оновлення з сервера зловмисника. Оновлення працюють як корінь, тому зловмисник має повний контроль.

Ви можете запобігти атаці, переконавшись, що оригінал справжній. Але для цього потрібно перевірити завантажене зображення з компакт-диска за допомогою хеша ( мало хто насправді це робить ) - і хеш сам повинен бути надійно завантажений, наприклад, через HTTPS. І якщо ваш зловмисник може додати сертифікат на свій комп’ютер (звичайний у корпоративному середовищі) або керувати органом сертифікації (наприклад, Китай), навіть HTTPS не захищає.


47
Це помилково. Компілятор повинен лише визначити, коли він збирає дуже специфічний вихідний файл із власного вихідного коду з дуже конкретним вмістом, а не коли він компілює будь-який компілятор !!!
Каз

14
@Kaz - У якийсь момент надпланові модифікації компілятора чи програми входу можуть потрапити до того моменту, коли вони переможуть компілятор-розпізнавач / вхід-розпізнавач backdoor, і наступні ітерації втратять задню панель. Це аналогічно випадковій біологічній мутації, що дає імунітет певним захворюванням.
Рассел Борогов

12
У першій половині вашої відповіді є проблема, яку описує Каз, але друга половина настільки гарна, що я все-таки стаю +1!
ruakh

7
Злий компілятор, який визнає лише власне джерело, легко створити, але на практиці відносно нікчемний - мало хто, у якого вже є двійковий файл цього компілятора, використовував би його для відтворення зазначених бінарних файлів. Щоб атака була успішною протягом більш тривалого періоду, компілятору знадобиться більше інтелекту, щоб виправити нові версії власного джерела, таким чином, зіткнувшись з проблемами, описаними в snswer.
користувач281377

5
Розпізнавальник для конкретного компілятора може бути досить загальним і навряд чи може зламатися перед новою версією. Візьмемо для прикладу gcc - багато рядків коду в gcc дуже старі і не дуже змінилися. Прості речі, такі як назва, майже ніколи не змінюються. Перед тим, як розпізнавання піде не так, швидше за все, введений код буде. Насправді обидві ці проблеми є в основному теоретичними - на практиці автор шкідливих програм не матиме жодних труднощів бути в курсі (повільних) темпів розробки компілятора.
Еймон Нербонна

25

По-перше, моя улюблена написання цього хака називається Strange Loops .

Цей конкретний злом, безумовно, може бути зроблений сьогодні у будь-якому з основних проектів ОС з відкритим кодом, зокрема Linux, * BSD тощо. Я би сподівався, що це буде працювати майже однаково. Наприклад, ви завантажуєте копію FreeBSD, яка має експлуатований компілятор для зміни opensh. Відтоді щоразу, коли ви оновлюєте opensh або компілятор за джерелом, ви продовжуватимете проблему. Якщо припустити, що зловмисник в першу чергу експлуатував систему, використовувану для упаковки FreeBSD (ймовірно, оскільки саме зображення пошкоджене або зловмисник насправді є пакувальником), то кожен раз, коли система відновлює бінарні файли FreeBSD, вона знову спричинить проблему. Існує маса способів невдачі цієї атаки, але вони принципово не відрізняються від того, як атака Кена могла зазнати краху (**). Світ насправді так сильно не змінився.

Звичайно, подібні атаки можуть так само легко (або простіше) вводитися їх власниками в такі системи, як Java, SDK для iOS, Windows чи будь-яку іншу систему. Певні вади безпеки можуть бути навіть вбудовані в апаратне забезпечення (особливо послаблення генерації випадкових чисел).

(*) Але під "звичайно" я маю на увазі "в принципі". Чи слід очікувати, що такий отвір існує в якійсь конкретній системі? Ні. Я вважав би це малоймовірним з різних практичних причин. З плином часу, коли код змінюється і змінюється, збільшується ймовірність того, що подібний злом спричинить дивні помилки. І це підвищує ймовірність того, що його виявлять. Менш геніальні закутки вимагатимуть змови на підтримку. Звичайно, ми знаємо на той факт, що "закордонний перехоплення" на задньому плані встановлено в різних системах телекомунікацій та мереж, тож у багатьох випадках цей складний злом зайвий. Злом встановлено відкрито.

Так завжди, оборона в глибину.

(**) Якщо припустити, що напад Кена колись фактично існував. Він просто обговорював, як це можна зробити. Він не сказав, що насправді це зробив, наскільки я знаю.


Щодо вашої другої виноски, Кен сказав "будувати та не поширювати".
8bittree

15

Це впливає на всі мови?

Ця атака в першу чергу зачіпає мови, які є самостійними. Тобто мови, де компілятор написаний самою мовою. На це вплинуть C, Squeak Smalltalk та інтерпретатор PyPy Python. Perl, JavaScript та інтерпретатор CPython Python не будуть.

Як це пов’язано із складанням щойно вчасно?

Не дуже. Саме хастинг компілятора дозволяє хавати хак. Я не знаю жодного самостійного розміщення компіляторів JIT. (Може, LLVM?)

Чи компілюються такі функції, як програма, що обробляє входи в системі * nix, під час їх запуску?

Не зазвичай. Але питання не в тому, коли він складений, а в тому, який компілятор . Якщо програма для входу складається компілятором, який буде порушено, вона буде пошкоджена. Якщо він складений чистим компілятором, він буде чистим.

Це все ще є дійсною загрозою, чи відбулися розробки в безпеці компіляції з 1984 року, які заважають цьому не стати важливим питанням?

Це все ще теоретична загроза, але це не дуже ймовірно.

Одне, що ви можете зробити, щоб пом'якшити це - це використовувати декілька компіляторів. Наприклад, компілятор LLVM, який сам складений GCC, не пройде уздовж задньої двері. Так само GCC, складений LLVM, не буде проходити вздовж задньої двері. Отже, якщо ви турбуєтесь про подібну атаку, ви можете скласти свій компілятор з іншою породою компілятора. Це означає, що злому хакеру (у вашого постачальника ОС?) Доведеться підбити обох компіляторів, щоб розпізнати один одного; Набагато складніша проблема.


Ваш останній абзац, строго кажучи, не відповідає дійсності. Теоретично код може виявити компілятор, який складається, і вивести задню двері належним чином. Це, звичайно, недоцільно в реальному світі, але немає нічого, що по суті, це не заважає. Але тоді початкова ідея полягала не в реальній практичній загрозі, а урок довіри.
Стівен Бернап

Справедливий пункт. Зрештою, хак підтримує бекдорд для входу, і мод для компілятора, тому він може нести мод і для іншого компілятора. Але це стає все більш малоймовірним.
Шон Макміллан

Якраз вчасно складання могло б стати частуванням. Якщо якийсь код має деяку вразливість лише тоді, коли певний фрагмент є компільованим JIT, він може залишитися непоміченим. (просто чистий тоєри)
GameDeveloper

12

Існує теоретичний шанс для цього. Однак існує спосіб перевірити, чи був порушений конкретний компілятор (із наявним вихідним кодом), завдяки подвійному компілюванню Девіда А. Уілера .

В основному використовуйте як підозрюваний компілятор, так і інший незалежно розроблений компілятор для складання джерела підозрюваного компілятора. Це дає SC SC і SC T . Тепер складіть підозріле джерело, використовуючи обидва ці двійкові файли. Якщо отримані бінарні файли ідентичні (за винятком різноманітних речей, які цілком законно можуть змінюватися, як, наприклад, різні часові позначки), підозрюваний компілятор насправді не зловживає довірою.


Або це, або довірений компілятор не такий надійний, як думав користувач. Але для двох незалежних реалізацій мови ймовірність того, що вони містять один і той самий бекдор, є незначною.
Damian Yerrick

Або інструмент diff, який ви використовуєте для їх порівняння, також був
порушений

@kennycoc Однак, написання інструменту порівняння "ці два файли однакові" не є всім, що вважається, складним (як у випадку з посиланням на систематичне виклик, його слід зробити за 2-16 годин у двійковому машинному коді).
Ватін

3

Що стосується конкретної атаки, це така ж загроза, як і колись, яка майже взагалі не загрожує.

Як це пов’язано із складанням щойно вчасно?

Не впевнений, що ти маєш на увазі під цим. Чи невпевнений у цьому JITter? Ні. Це вразливіше? Не зовсім. Як розробник ВАШ додаток є більш вразливим просто тому, що ви не можете перевірити, що це не було зроблено. Зауважте, що ваш поки що нерозвинений додаток в основному не захищений від цього та всіх практичних варіацій, вам потрібно потурбуватися лише про компілятор, який буде новішим за ваш код.

Чи компілюються такі функції, як програма, що обробляє входи в системі * nix, під час їх запуску?

Це не дуже актуально.

Це все ще є дійсною загрозою, чи відбулися розробки в безпеці компіляції з 1984 року, які заважають цьому не стати важливим питанням?

Справжньої безпеки компіляції немає, і бути не може. Це був справді сенс його розмови, що в якийсь момент ти повинен комусь довіряти.

Це впливає на всі мови?

Так. Принципово, в той чи інший час ваші вказівки повинні бути перетворені на щось, що виконується на комп’ютері, і це переклад можна зробити неправильно.


-2

У Девіда Уілера є хороша стаття: http://www.dwheeler.com/trusting-trust/

Мене більше хвилюють апаратні атаки. Думаю, нам потрібна цілком ланцюжок інструментів VLSI для проектування з вихідним кодом FLOSS, щоб ми могли змінювати та компілювати себе, що дозволяє нам створити мікропроцесор, який не містить вставлених інструментами задній план. Інструменти також повинні дати нам зрозуміти призначення будь-якого транзистора на мікросхемі. Тоді ми могли б відкрити зразок готових мікросхем і оглянути їх за допомогою мікроскопа, переконавшись, що вони мають таку саму схему, що і інструменти сказали, що вони повинні мати.


3
-1, основна частина вашої відповіді не відповідає на це питання.

-3

Системи, в яких кінцеві користувачі мають доступ до вихідного коду, - це ті, для яких вам доведеться приховати цей тип атаки. Це були б системи з відкритим кодом у сучасному світі. Проблема полягає в тому, що хоча існує залежність від одного компілятора для всіх систем Linux, атака мусить потрапити на сервери збірки для всіх основних дистрибутивів Linux. Оскільки вони не завантажують бінарні файли компілятора безпосередньо для кожного випуску компілятора, джерело для атаки мало б бути на їх серверах збірки як мінімум в одному попередньому випуску компілятора. Інакше це чи сама перша версія компілятора, яку вони завантажили у вигляді двійкового файлу, мала б бути порушена.


2
Ваша відповідь чухається на поверхні запитання, але насправді не відповідає тому, що вам задають.

-4

Якщо у вас є вихідний код для системи компілятора / збірки, вихід якої не повинен залежати ні від чого іншого, ніж від вмісту наданих вихідних файлів, і якщо один має кілька інших компіляторів і знає, що не всі вони містять один і той же хакер компілятора, можна переконайтеся, що ви отримуєте виконуваний файл, який не залежить від іншого, крім вихідного коду.

Припустимо, у кожного є вихідний код для пакета компілятора / лінкера (скажімо, Groucho Suite), написаного таким чином, що його вихід не буде залежати ні від не визначеного поведінки, ні від іншого, крім вмісту вхідних вихідних файлів, і одного компіляції / посилання, що кодують на різні незалежно створені компілятори / пакети лінкерів (скажімо, Harpo Suite, Chico Suite та Zeppo Suite), даючи різний набір виконуваних для кожного (називайте їх G-Harpo, G-Chico та G-Zeppo). Ці виконавчі файли не будуть несподіваними містити різні послідовності інструкцій, але вони повинні бути функціонально однаковими. Однак доведення їх функціональної ідентичності у всіх випадках, ймовірно, буде нерозв'язною проблемою.

На щастя, такий доказ не знадобиться, якщо ви використовуєте одержані виконувані файли лише для однієї цілі: складання пакету Groucho заново. Якщо один компілятор набору Groucho використовує G-Harpo (вихід GG-Harpo), G-Chico (GG-Chico) і G-Zeppo (GG-Zeppo), то всі три отримані файли, GG-Harpo, GG-Chico , і GG-Zeppo, всі байти за байтом повинні бути однаковими. Якщо файли збігаються, це означатиме, що будь-який "вірус компілятора", який існує в будь-якому з них, повинен існувати однаково у всіх (оскільки всі три файли є однаковими байт-байтами, жодного способу їх поведінки не може відрізнятися в будь-якому випадку спосіб).

Залежно від віку та походження інших компіляторів, можливо, переконатися, що такий вірус не міг би правдоподібно існувати в них. Наприклад, якщо хтось використовує антикварний Macintosh для годування компілятора, який був написаний з нуля в 2007 році через версію MPW, написану в 1980-х, компілятори 1980-х років не знають, куди вставити вірус у компілятор 2007 року. Сьогодні компілятору можливо зробити достатній фантазійний аналіз коду, щоб зрозуміти це, але рівень обчислень, необхідний для такого аналізу, набагато перевищив би рівень обчислень, необхідний для простого складання коду, і не міг би пройти непомітно на ринку, де швидкість компіляції була головною точкою продажу.

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

З іншого боку, я не впевнений, що я бачу будь-яку причину того, що компілятори / посилання не повинні забезпечувати режим "канонічного виводу", коли вихід залежить тільки від вихідних файлів та "дати компіляції", яку користувач може змінити . Навіть якщо компіляція коду в такому режимі зайняла вдвічі більше, ніж звичайна компіляція, я б припустив, що було б велике значення в змозі відтворити будь-яку "версію версії", байт за байтом, повністю з вихідних матеріалів, навіть якщо це означало, що Збірки версій зайнятимуть більше часу, ніж "звичайні збірки".


2
-1. Я не бачу, як ваша відповідь стосується основних аспектів питання.

@ GlenH7: Багато старих інструментів компіляції послідовно дають бітовий ідентичний вихід при наданні бітового ідентичного вводу [поза такими речами, як TIME , які можна змінити, щоб повідомити про "офіційний" час компіляції]. Використовуючи такі інструменти, можна досить добре захистити від вірусів компілятора. Той факт, що деякі популярні рамки розробки не дають можливості "детерміновано" складати код, означає, що методи, які могли захищати від вірусів у старих інструментах, не можуть бути ефективно використані з новішими.
supercat

Ви пробували це? 1. Ведіть із своєю тезою. 2. Використовуйте коротші абзаци. 3. Будьте більш чіткими щодо різниці між "функціонально однаковими" (результат першого етапу) і "бітовими ідентичними" (результат другого), можливо, зі списком усіх створених бінарних файлів компілятора та їх взаємозв'язків між собою. 4. Цитуйте документ DDC Девіда А. Уілера.
Damian Yerrick
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.