Анализ защищенности приложений для операционной системы Android

Возможность запуска вредоносного приложения в контексте доверенного приложении. Использование программного варианта KeyStore. Применение локальной базы данных для хранения конфиденциальной информации. Использования Deep Links для передачи данных.

Рубрика Программирование, компьютеры и кибернетика
Вид дипломная работа
Язык русский
Дата добавления 14.07.2020
Размер файла 370,7 K

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.

Размещено на http://www.allbest.ru/

ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ АВТОНОМНОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ОБРАЗОВАНИЯ

«НАЦИОНАЛЬНЫЙ ИССЛЕДОВАТЕЛЬСКИЙ УНИВЕРСИТЕТ

«ВЫСШАЯ ШКОЛА ЭКОНОМИКИ»

Факультет Бизнеса и Менеджмента

Выпускная квалификационная работа

«Анализ защищенности приложений для ОС Android»

студента

Гузовса Рихардса

Научный руководитель

А.М. Чеповский

Москва 2020

ОГЛАВЛЕНИЕ

ОПРЕДЕЛЕНИЯ, ОБОЗНАЧЕНИЯ И СОКРАЩЕНИЯ

ВВЕДЕНИЕ

ГЛАВА 1. ВВЕДЕНИЕ В АРХИТЕКТУРУ ОС ANDROID

1.1 Java

1.2 Kotlin

1.3 Архитектурные уровни ОС Android

1.4 Модель безопасности ОС Android

ГЛАВА 2. ИССЛЕДОВАНИЕ НЕДОСТАТКОВ ДЛЯ ОС ANDROID

2.1 Activity

2.1.1 Стек Activity

2.1.2 Возможность запуска вредоносного приложения в контексте доверенного приложении

2.1.3 Наличие флага android:exported для Activity со значением True

2.2 Content Provider

2.2.1 Контроль доступа

2.2.2 Отсутствие вызова revokeUriPermission() после выдачи разрешения стороннему приложению

2.2.3 Использование метода openFile()

2.2.4 SQL-инъекции

2.3 Хранение данных

2.3.1 Android Keystore

2.3.2 Использование программного варианта KeyStore

2.3.3 Ключ не инвалидируется при добавлении нового отпечатка пальца

2.3.4 Использование атрибутов MODE_WORLD_READABLE и MODE_WORLD_WRITABLE для создания файлов

2.3.5 Использование SharedPreferences для хранения конфиденциальной информации

2.4 WebView

2.4.1 Поддержка JavaScript

2.4.2 Доступ к локальным ресурсам устройства

2.5 Broadcast receiver

2.5.1 Некорректная реализация контроля доступа при отправке широковещательных сообщений

2.5.2 Небезопасная реализация контроля доступа при получении широковещательных сообщений

2.5.3 Отсутствие валидации схемы при открытии в loadUrl() WebView

2.5.4 Использование setAllowFileAccessFromFileURLs и setAllowUniversalAccessFromFileURLs

2.6 Deep links

2.6.1 Использования Deep Links для передачи конфиденциальных данных

2.7 Транспортная безопасность

2.7.1 Отсутствие требования использования HTTPS соединений

2.7.2 Certificate Pinning

2.7.3 Отсутствие Certificate Pinning

2.8 Прочее

2.8.1 Использование директивы android:installLocation со значением “auto”

2.8.2 Отсутствие FLAG_SECURE у Activity в которых отображается конфиденциальная информация

2.8.3 Риск утечки данных через вредоносную клавиатуру

2.8.4 Чрезмерное использования логирования в приложении

2.8.5 Использование allowBackup со значением true в AndroidManifest.xml приложения

2.8.6 Использование android:debuggable в продукционной версии приложения

ГЛАВА 3. РЕЗУЛЬТАТЫ АНАЛИЗА

ЗАКЛЮЧЕНИЕ

ОПРЕДЕЛЕНИЯ, ОБОЗНАЧЕНИЯ И СОКРАЩЕНИЯ

Аббревиатура

Расшифровка

БД

База данных

ИБ

Информационная безопасность

ИТ

Информационные технологии

ОС

Операционная система

ПО

Программное обеспечение

CWE

Common Weakness Enumeration. http://cwe.mitre.org/

WASC

Web Application Security Consortium, http://www.webappsec.org/

OWASP

Open Web Application Security Project. http://www.owasp.org/

SSL

Secure Socket Layer

TLS

Transport Layer Security

XML

Extensible Markup Language

XXE

XML External Entity

CSRF

Cross-Site Request Forgery. Атака, связанная с возможностью подделки межсайтовых запросов

MITM

Техника атак «человек в середине» (Man In The Middle), направленная на компрометацию сессии между клиентом и сервером

DoS

Denial of Service - отказ в обслуживании

XSS

Cross-Site Scripting. Атака, межсайтового скриптинга

SQL

Structured Query Language

Root

Root или суперпользователь - это учетная запись в UNIX-подобных системах с наивысшими правами.

ВВЕДЕНИЕ

Android - это операционная система, предназначенная для мобильных устройств. Если точнее, то Android можно рассматривать как программный стек, одной из составляющих которого является ОС, построенная на ядре Linux и среды выполнения Dalvik Virtual Machine (в более новых версиях - Android Runtime [11, 12]).

Android позиционируется как платформа с открытым исходным кодом, однако Android отличается от классических Open Source продуктов тем, что процесс разработки Android полностью сосредоточен в компании Google, а сторонним разработчикам предоставляют открытый исходный код уже готового продукта.

Подобный подход отличен от классического подхода в работе над Open Source проектами тем, что нет так называемого «базара», в котором каждый сторонний разработчик имеет возможность предложить или внести какие-то изменения перед выпуском финального продукта.

Однако, благодаря открытости исходного кода и тому, что изначально официальный язык Android - это Java (с 2019 года компания Google объявила о том, что рекомендуемый язык для написания кода для приложений под ОС Android - Kotlin [48]), то Android можно переносить на различные устройства, внедрять в него собственные приложения, механизмы защиты, оболочки и сервисы.

Также Android позволяет персонализировать устройство множеством различных способов. Согласно заявлению, сделанному на официальной странице Android в социальной сети Twitter, то по состоянию на 7 мая 2019 года общее количество активных устройств с Android составляло более 2.5 миллиардов [31] по всему миру.

О популярности устройств с Android также говорят и статистические данные касательно наиболее используемых мобильных ОС.

По состоянию на декабрь 2019 года, соотношение было следующим [54]:

Рис 1. Процентное соотношение ОС для мобильных устройств.

Отчетливо видно, что популярность ОС Android значительно выше, чем у множества других ОС.

Такая популярность не могла не привлечь злоумышленников и ОС Android стала целью множества хакерских атак.

Например, в официальном магазине приложений Google Play была обнаружена вредоносная игра “ZOMBYE Mod: The Horror Game 2019” которая воровала конфиденциальные данные пользователей [49], троян “FANTA” нацеленный на пользователей сервиса “Avito” [7], вредоносное приложение “xHelper” [35] и множество других.

Что касается безопасности мобильных приложений, то согласно исследованию компании “NowSecure” проведенному в 2018 году, почти 50% исследованных приложений использовали небезопасный функционал хранения данных, небезопасную реализацию коммуникаций клиент-сервер или использовали скрытую (англ. Extraneous) функциональность, которая позволяла скомпрометировать приложение [40].

Хоть исследование и было направлено на приложения, написанные как для Android, так и iOS, оно все еще предоставляет интерес для данной работы т. к. проблематика мобильных приложений в целом достаточно схожа.

Если рассмотреть конкретно приложения для ОС Android, то по мнению специалистов из компании “Positive Technologies”, 38% исследованных приложений используют небезопасные механизмы межпроцессной коммуникации, 25% приложений позволяют создавать резервную копию при подключении мобильного устройства к компьютеру, 41% мобильных приложений осуществляют проверку аутентификационных данных на стороне клиента [55].

Основополагающим трудом, который на момент написания данной работы используется в качестве методического пособия для анализа мобильных приложения является “OWASP Top 10 Mobile Risks 2016” [53], которой классифицирует недостатки следующим образом:

1. Обход архитектурных ограничений

2. Небезопасное хранение данных

3. Небезопасная передача данных

4. Небезопасная аутентификация

5. Слабая криптостойкость

6. Небезопасная авторизация

7. Контроль содержимого клиентских приложений

8. Модификация данных

9. Анализ исходного кода

10. Скрытый функционал

Данная методология принимается в качестве стандарта при анализе защищенности мобильных приложений, т. к. она предлагает достаточно широкий список недостатков, выявление которых необходимо при анализе защищенности мобильного приложения.

Однако, методология не дает оценки критичности описываемых недостатков и предлагает исключительно инструкцию по их нахождению, полнота которой иногда не является достаточной.

В дополнение, данная методология датирована 2016 годом и учитывая темпы развития информационных технологий, некоторые вещи устаревают и не происходит своевременного добавления новых векторов атаки.

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

В рамках данной работы будут рассмотрены различные недостатки безопасности приложений для ОС Android, их реализация в коде приложения, рассмотрены вектора атак и способы компрометации данных через эксплуатацию недостатков.

К подобным работам одной из задач которых было систематизировать обнаруженные в коде приложения недостатки, можно отнести исследование, именуемое “Security code smells in Android ICC”, одним из результатов которого стала реализация скрипта позволяющего в автоматизированном режиме обнаружить в исходном коде приложения для Android намеки на те или иные недостатки. Также, исследователи проанализировали большое количество приложений с использованием написанного ими скрипта, чтобы выявить наиболее часто встречающиеся недостатки (точнее, куски кода, которые потенциально могут быть недостатками) [41].

В прочем, как это и типично для статических анализаторов кода, данный скрипт не мог определить действительно ли является найденный участок кода, который соответствует определенному шаблону недостатком. Это еще раз подтверждает, что наиболее трудной и актуально задачей является именно ручной анализ исходного кода на предмет наличия того или иного недостатка безопасности.

Также, стоит отметить, что написанный в рамках упомянутого исследования скрипт не является единственным в своем роде, существуют и другие анализаторы, с функциональностью анализа защищенности исходного кода мобильного приложения. [32]

Тем не менее, ценность работы заключается также в том, что она предлагает свою собственную классификацию недостатков, а также рассматривает различные недостатки, которые не были внесены в OWASP.

На данный момент исследовательские работы, связанные с анализом защищенности мобильных приложений, в частности в сфере анализа исходного кода на предмет недостатков безопасности являются достаточно разрозненными и чаще всего направлены на изучение конкретных недостатков безопасности, нежели пытаются объединить и предложить альтернативную и подробную методологию, направленную на анализ «в ширину».

Тем не менее, встречаются некоторые попытки независимых исследователей, однако работы подобного характера носят характер шпаргалки, нежели объёмной работы. [50]

Также, следует выделить ряд статей, опубликованных на сайте компании “RayWanderlich” которые содержат различные примеры лучших практик и методик по реализации тех или иных компонентов безопасности мобильного приложения для ОС Android и, что важно, статьи основаны на использовании языка Kotlin, а также наиболее актуальных библиотек для разработки мобильных приложений. [60, 61, 62]

Несмотря на то, что большинство статей описывающих недостатки безопасности приложений написанных под ОС Android достаточно стандартизированы и во многом повторяются, отдельно можно выделить достаточно много детализированных работ, которые описывают критичные недостатки, например, связанные с такими сложными компонентами экосистемы Android как “Android Keychain” написанную исследователями из компании “F-Secure Labs” которая описывают актуальные на сей день атаки и также достаточно подробно разъясняет каким образом можно обезопасить приложение. [33]

Еще одним примером работы, описывающей нестандартные техники атаки и защиты на приложение, является написанная Юрием Шибалиным и Евгением Блашко статья на тему архитектурного недостатка платформы Android связанного с возможностью подмены экрана действующего приложения. [1]

Некоторые, более специфические статьи, например, посвященные вопросам безопасности таких компонентов как Content Provider, WebView и т. д. достаточно легко доступны и весьма популярны китайском сегменте сети Интернет. [65, 66]

В таких статьях делается упор на подробном описании потенциальных атак на упомянутые компоненты. Достаточно интересно, что статьи, описывающие атаки на небезопасно сконфигурированные компоненты типа WebView популярны в китайском сегменте, однако, достаточно скромно описываются в европейском. Также одним из важных преимуществ статей китайских авторов в том, что они обильно снабжены примерами кода, хоть и, к сожалению, чаще всего ограничены языком Java.

Из вышесказанного можно резюмировать следующее:

1. Несмотря на наличие автоматизированных способов анализа исходного кода мобильного приложения, необходимость ручного анализа до сих пор актуальна.

2. Существует ряд статей описывающих те или иные недостатки безопасности приложений, однако отсутствует соответствующий текущим проблемам и недостатка систематизированной позволяющей эффективно и быстро находить информацию по способу обнаружения и исправления актуальных недостатков.

Соответственно, задачи и цели данной работы можно формализовать следующим образом - агрегировать и проанализировать уже имеющуюся, доступную информацию по поиску актуальных по сей день недостатков безопасности мобильных приложений, объединить ее с информацией касающийся лучших практик в написании безопасного кода и предоставить ее в структурированном виде, позволяющем с достаточной простотой переносить прочитанные в работе концепции на практическую деятельность.

Также, автор работы постарается предложить некоторые альтернативные решения в контексте подхода к анализу защищенности мобильных приложений и поиска недостатков.

В рамках работы, автором будут рассмотрены недостатки следующих категорий:

1. Недостатки, связанные с Activity.

2. Недостатки, связанные с Content Provider.

3. Недостатки, связанные с хранением данных.

4. Недостатки, связанные с компонентом WebView.

5. Недостатки, связанные с механизмом DeepLinks.

6. Недостатки, связанные с механизмом транспортной безопасности (передачи данных).

7. Прочие недостатки.

Справедливо будет заметить, что рассмотреть все недостатки каждой из категории не предоставляется возможным, таким образом решение о рассмотрении того или иного недостатка будет базироваться на общей статистики и субъективного видения автора, основанного на опыте работы в сфере анализа защищенности мобильных приложений.

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

Актуальность работы заключается в том, что этот текст может быть использована как разработчиками для реализации более безопасного кода мобильных приложений, так и аналитиками информационной безопасности, пентестерами при анализе защищенности приложений для ОС Android.

Методология

Принимая во внимание, что данная работа подразумевает исследование и систематизацию недостатков безопасности для приложений, написанных под операционную систему Android, то методология, к которой прибегал автор работы в процессе ее написания может быть формализована следующим образом:

1. Изучение доступных ресурсов, книг и статей описывающих необходимые для понимания архитектурные компоненты ОС Android, например Activity, Content Provider и т. д.

2. Формирование краткого описания наиболее важных с точки зрения безопасности архитектурных компонентов ОС Android.

3. Изучение теоретических материалов, связанных с различными недостатками безопасности приложений для ОС Android.

4. Агрегирование полученной информации и создание выборки недостатков безопасности для дальнейшего практического изучения в рамках данной работы.

5. Практическое изучение недостатков, которое подразумевает написание тестовых приложений с тем или иными недостатком.

6. Формирование отчета по каждому из недостатков, который имеет следующую структуру:

6.1 Описание недостатка.

6.2 Описание процесса обнаружения недостатка.

6.3 Анализ критичности.

6.4 Рекомендации по устранению.

7. Формирование выводов и заключение.

Стоит сказать несколько слов касательно анализа и оценивания критичности недостатков безопасности приложений для платформы Android. В практической деятельности, процесс оценивания критичности того или иного недостатка является достаточно трудным занятием, т. к. имеет под собой множественные факторы и часто является весьма субъективным суждением. Данная работа не будет исключением и в качестве оценки критичности недостатков, описанных далее в работе, будет предложен субъективный взгляд автора, где за основу рассматривается потенциальный ущерб от реализации недостатка на сложность его реализации.

Модель нарушителя

Во время анализа защищенности мобильного приложения, в качестве модели нарушителя можно рассматривать следующие сценарии:

“Человек посередине”. Злоумышленник, который находиться с легитимным пользователем мобильного приложения в одной сети Wi-Fi. Злоумышленник потенциально может проводить атаки, нацеленные на перехват и модификацию трафика пользователя.

Вредоносное приложение. Приложение, установленное и работающее на том же устройстве, что и целевое мобильное приложение. Мобильной устройство работает в контексте стандартных привилегий. Атака происходит на клиента.

Украденное или потерянное мобильное устройство. Речь идет о том, что злоумышленник имеет возможность получить полный физический доступ к устройству пользователя мобильного приложения.

Доступ к резервной копии телефона. Злоумышленник имеет возможность получить доступ к файлу с архивной копией мобильного устройства.

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

Структура работы

Первая часть состоит из описания общих концепций и характеристик ОС Android, ее архитектура, модель безопасности, будет предложена модель нарушителя. Также произойдет краткое описание языков Java и Kotlin.

Вторая часть полностью посвящена анализу недостатков безопасности приложений для ОС Android, будут рассмотрены различные недостатки и вектора их компрометации.

Третья часть посвящена выводам, будет предложен альтернативный взгляд на процесс анализа защищенности мобильного приложения.

ГЛАВА 1. ВВЕДЕНИЕ В АРХИТЕКТУРУ ОС ANDROID

1.1 Java

На сегодняшний день язык программирования Java является одним из самых популярных среди программистов.

Если верить ежегодному опросу веб-сайта “Stack Overflow”, то в 2019 году Java занимает пятое место среди наиболее популярных технологий. [59]

Дополнительно, можно обратиться к индексу TIOBE который рассчитывается, учитывая количество поисковых запросов по заданной тематике.

Согласно индексу, то за Апрель 2020 года - Java самый популярный язык программирования. [64]

Выпуск первой версии языка Java состоялся в 1995 году.

Изначально, язык создавался Джеймсом Гослингом (англ. James Arthur Gosling) в недрах компании Sun Microsystems, и в качестве планируемого названия был выбран Oak.

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

В нынешнее время язык Java успешно используется для написания программного обеспечения для множества устройств, например, персональные компьютеры, бытовой техники, мобильных устройств и т. д. Язык Java успешно используется как для написания клиентских и серверных приложений.

Java - это язык программирования общего назначения со строгой типизацией. Является частью множества объектно-ориентированных языков программирования.

При проектировании языка Java, создатели руководствовались принципом “WORA: write once, run anywhere” или на русском - “пиши один раз, запускай везде”.

Важнейшей составляющей платформы Java -- это виртуальная машина, которая позволяет запустить написанную на языке Java программу на любой платформе, при условии, что на ней установлена среда выполнения JRE. [43]

Это возможно благодаря тому, что происходит компиляция код написанного на языке Java в специализированный набор инструкций или байт-код (англ. byte-code).

После чего байт-код выполняется виртуальной машиной Java - JVM, которая является частью среды выполнения JRE.

Реализация подобного архитектурного решения обеспечивает кроссплатформенность написанных на языке Java программ, благодаря чему подобные программы, в дальнейшем, без перекомпиляции могут выполняться на множестве платформ - Windows, Linux, Mac OS и т. д.

На каждой из вышеупомянутых платформ может быть своя реализация виртуальной машины Java, но независимо от этого, каждая из них может выполнять один и тот же набор инструкций. [2]

Java является языком с С-подобным синтаксисом и близок в этом отношении к C/C++ и C#. В качестве примера можно рассмотреть код программы “Hello World” на C# и Java.

Пример на C#:

using System;

public class Program

{

public static void Main()

{

Console.WriteLine("Hello World");

}

}

Пример 1: “Hello World”.

Пример на Java:

public class HelloWorld {

public static void main(String[] args) {

System.out.println("Hello World!");

}

}

Пример 2: “Hello World”.

Из важных особенностей - в языке Java реализован механизм управления памятью, который называется сборщиком мусора (англ. garbage collector), который освобождает программиста от необходимости вручную освобождать память от более неиспользуемых составных частей программы, т. к. сборщик мусора сделает все автоматически.

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

Также, в программах, написанных на языке Java отсутствует, угроза атак типа переполнения буфера так как если во время выполнения кода компонент программа пытается выйти за пределы выделенного участка памяти, виртуальная машина выдаст исключение, и программа остановится. [5]

1.2 Kotlin

Язык Kotlin - достаточно молодой язык программирования, который был создан российской компании JetBrains и вышел в свет в 2011 году.

На конференции Google I/O 2017 команда разработчиков платформы Android сообщила, что Kotlin получил официальную поддержку для разработки Android-приложений. [10]

Согласно ежегодной статистике веб-сайта “Stack Overflow” за 2019 год, Kotlin занял четвертую позицию среди наиболее любимых языков программирования среди разработчиков. [58]

Kotlin, как и Java работает поверх JVM и при компиляции трансформируется в байт-код.

Иными словами, существует возможность запускать приложения написанные на Kotlin везде, где установлена JVM.

Добавим, что Kotlin также можно компилироваться в качестве JavaScript и запускать в браузере.

Помимо этого, существует возможно компилирования в нативные бинарные файлы, которые будут работать без виртуальной машины. [62]

Таким образом, множество платформ, которые будут поддерживать приложения написанные на Kotlin, чрезвычайно широк, это - Windows, Linux, Mac OS, iOS, Android.

Kotlin статически типизированный язык. Он поддерживает как объектно-ориентированное, так и процедурное и функционально программирование.

Одним из огромных плюсов Kotlin заключается в том, что код написанный на Kotlin полностью совместим с кодом написанным на Java и поддерживает все Java-фреймворки и библиотеки.

Null-безопасность

Kotlin не допускает возникновения NullPointerException, выдавая ошибку компиляции.

В случае попытки присвоить или вернуть тип null, код не скомпилируется.

Тем не менее в случае, если использования типа null необходимо, то это возможно осуществить, добавив знак вопроса (?) к названию типа, например вот так: [45]

val name: String? = null

Пример 3: тип null.

Функции-расширения

Аналогично таким языкам программирования, как, например, упомянутый C#, язык Kotlin позволяет расширить класс путём добавления новой функциональности, не прибегая к наследованию.

Такая возможность реализована с помощью специализированных расширений. [46]

Классы данных

В языке Kotlin появились специализированные классы, предназначение которых заключается в том, что бы использовать их для хранения данных. [47]

data class Book(var title:String,var author:Author)

Пример 4: классы данных.

Функциональное программирование

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

Язык предлагает множество полезных свойств, например, функции высшего порядка или функции, которые способны принимать в качестве аргумента другие функции, лямбда-выражения, перегрузку операторы и т. д.

На данный момент, согласно статистике в 66% случаев программисты используют Kotlin для написания приложений на платформе Android, в 57% для виртуальной машины Java. В 5% случаев Kotlin используется для написания кода для JavaScript. [44]

Добавим, что примеры, используемые в рамках данной работы, будут написаны на языке Kotlin.

1.3 Архитектурные уровни ОС Android

Раздел посвящен краткому введению в архитектуру ОС Android. Предоставленные ниже сведения предназначена для того, чтобы сформировать высокоуровневое представления об архитектуре операционной системы.

Информация, которая необходима непосредственно для понимания некоторых аспектов данной работы будет выделена и рассмотрена более подробно.

Архитектура платформы Android состоит из нескольких уровней, которые мы рассмотрим ниже.

Уровень ядра

В Android в качестве ядра используется модифицированная версия ОС Linux, если точнее, то именно часть операционной системы Linux - Kernel.

Задача Kernel - управлять памятью и процессами, работает с драйверами, сетью и также Kernel выступает в роли слоя аппаратных абстракций (англ. Hardware Abstraction Layer). [3]

Слой аппаратных абстракций или HAL -- это набор интерфейсов, реализация которых произведена в драйверах.

Среди них следует отметить следующие:

1.1 Драйвер меж процессного взаимодействия.

1.2 Драйвер дисплея.

1.3 Wi-Fi драйвер.

1.4 Менеджер управления энергопотреблением и т. д.

Иными словами, Linux Kernel является слоем абстракции между аппаратной и программной часть платформы.

На уровне ядра также реализован один из наиболее важных аспектов безопасности ОС Android. Это так называемый Binder. Данный механизм ответственен за меж процессные коммуникации и будет рассмотрен более детально в следующей главе. [13]

Уровень среды исполнения

В качестве среды исполнения до версии Android 5.0, использовалась виртуальная машина Dalvik.

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

Dalvik не использует байт-код Java, а использует свой собственный, который является производным от Java - Dalvik Executable (*.dex), оптимизированный для минимального использования памяти приложением.

В сравнении с виртуальной машиной Java, Dalvik был изначально сконфигурирован для мобильной ОС и имел цель быстродействие, взаимодействие с батареей и т. д. [38]

Dalvik является представителем регистро-ориентированных виртуальных машин.

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

С выходом в свет Android версии 4.4 появилась экспериментальный экземпляр новой виртуальной машины, которая в последствии и заменила Dalvik. [8]

Речь идет о так называемом ART - Android Run Time.

Отличие ART от Dalvik заключается в том, что при установке приложения, код сразу компилируется в машинный код, в то время как при использовании виртуальной машины Dalvik происходила компиляция, вначале, из Java-кода в байт-код с расширением “dex” (англ. Dalvik executable), а затем уже запускались программы и происходила компиляция в машинный код в реальном времени.

Соответственно, ART является более экономичным с точки зрения энергоресурсов.

Однако, побочным эффектом является увеличение используемого дискового пространства и замедления скорости установки приложения. [38]

В целом интеграция такой виртуальной машины как ART стала возможной в связи с тем, что произошло увеличение объёма памяти в мобильных устройствах.

Уровень нативных С/C++ библиотек

Достаточно много ключевых компонентов и сервисов ОС Android, например HAL, ART состоят из кода, который использует библиотеки написанные на C и C++. Платформа Android предоставляет специализированные Java API которые позволяют приложения обращаться к компонентам, написанным на упомянутых языках. Например, приложение может воспользоваться Open GL используя Java API для того, чтобы производить манипуляцию с 2/3D графикой в приложении.

Уровень фреймворков Java API

Данный уровень ответственен за предоставление приложению доступов к различным компонентам ОС Android предоставляемым через Java API, например, менеджер оповещений, поставщики данных, менеджер ресурсов и т. д. [14]

Уровень системных приложений

Самый последний уровень состоит из системных приложений, которые идут предустановленными вместе с мобильным устройством, например, приложение для звонков, отправки смс и т. д.

Имея некоторое представление об архитектуре ОС Android, можно переходить к рассмотрению некоторых аспектов ее модели безопасности.

1.4 Модель безопасности ОС Android

В данное главе будет предоставлена информация касательно основных аспектов модели безопасности ОС Android, которые напрямую связаны с материалом, предоставленным в следующих главах.

Будут рассмотрены такие важные компоненты как binder, изоляция процессов, система разрешений.

“Sandboxing” приложений

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

В процессе установки ОС Android присваивает каждому приложению уникальное значение user ID (UID) и group ID (GID), тем самым для каждого приложения в ОС создается своей пользователь.

Важно отметить, что пользователи создаются с минимальными привилегиями. Каждому приложению выделяется отдельная директория в каталоге /data/data/ и доступ к которой может быть только у этого приложения (за некоторыми исключениями, которые будут рассмотрены далее в работе).

Таким образом происходит изоляция приложения как на уровне процесса, так и на уровне файлового хранилища.

Добавим, что существует критически важные процессы, пользователи которых «зашиты» внутри ОС и данные процессы выполняются от имени высоко привилегированных пользователей (некоторые с правами суперпользователя). [39]

Система разрешений

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

Таким образом, приложение может запрашивать специальные разрешения на доступ к ресурсам устройства, интернету, данным другого приложения и т. д.

Приложение может запрашивать разрешения явно указав их в файле-манифесте приложение AndroidManifest.xml, пример такого файла можно увидеть ниже: [15]

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.example.someapp">

<uses-permission android:name="android.permission.INTERNET"/>

<application …>

...

</application>

</manifest>

В примере выше, приложение запрашивает доступ на использование сети Интернет.

Существуют три уровня разрешений - нормальные разрешения, опасные разрешения и подписанные разрешения. Рассмотрим каждый по отдельности.

Нормальные разрешения относятся к случаям, когда приложения запрашивают доступ к компонентам или файлам вне своей “песочницы”, но где минимальный риск компрометации конфиденциальных данных пользователя. Например, разрешение использования временной зоны относится к таким разрешениям. Если приложение запрашивает такое разрешение, то система автоматически его выдаст.

Опасные разрешения - разрешения, которые относятся к случаям, когда доступ запрашивается к компонентам или данным, которые содержат или могут раскрыть конфиденциальную информацию пользователя. Например, возможность чтения контактов пользователя из телефонной книги является таким разрешением.

Если приложению необходимо пользоваться компонентами, доступ к которым возможен только в случае использования опасных разрешений, то пользователь должен сам выдать его во время выполнения программы. С точки зрения интерфейса пользователя, это выглядит как всплывающее окно, в котором будет содержаться информация о запрашиваемом разрешении и собственно возможность дать его или отказать. [15]

Подписанные разрешения

Разрешения данного типа выдаются ОС Android во время установки приложения. Однако, должно быть соблюдено условие - приложение должно быть подписано тем же сертификатом, что и приложение, которое определяет запрашиваемые разрешения.

Добавим, что в официальной документации Android явно определены разрешения для каждого из указанных типов.

Binder

Данный компонент, как и уже говорилось ранее, ответственен за межпроцессные коммуникации. Постараемся чуть более подробно рассмотреть основные принципы работы этого механизма.

Процедура взаимодействия между разными процессами в ОС Android организована используя синхронную модель клиент-сервер. Клиент инициирует соединение и ждет ответа со стороны сервера. Следовательно, такое взаимодействие между двумя участниками - клиент и сервер происходит последовательно.

Это архитектурное решение дает возможность разработчику вызывать разные методы словно они находятся в одном и том же процессе.

В рамках Binder меж процессные взаимодействия между приложениями происходят используя специализированный Linux Device Driver - /dev/binder.

Чтобы взаимодействие с этим драйвером было возможно, существует библиотека libbinder. Она позволяет осуществить процесс работы с данными драйвером достаточно прозрачным для разработчика. Например, клиент-серверное взаимодействие производиться через специализированное прокси на стороне клиента, и так называемом stub на серверной стороне. Они отвечают за процесс передачи команд между клиентом и сервером.

Откровенно говоря, сам механизм Binder не отвечает за безопасность.

Но тем не менее, Binder предлагает некоторые механизмы для ее обеспечения. Например, в каждую передачу данных Bunder добавляет UID и PID инициирующего процесса. Сервис, который к которому происходит обращение может решить, следует ли ему выполнять запрос, или нет. [4]

ГЛАВА 2. ИССЛЕДОВАНИЕ НЕДОСТАТКОВ ДЛЯ ОС ANDROID

В этой части мы рассмотрим основные компоненты операционной системы Android, недостатки, которые могут возникнуть при их реализации, а также вектора атак на них.

2.1 Activity

Activity -- это компонент приложения, который отображается на экране, и с которым пользователь может взаимодействовать для выполнения каких-либо действий, например написать смс-сообщение, сделать фотографию или видео, отправить электронное письмо или просмотреть новости.

Каждому такому действию присваивается окно для отображения, соответствующего Activity.

Иными словами, Activity -- это отдельный графический интерфейс, в котором отображаются определенные элементы доступные для пользователя в рамках Activity.

Все Activity определены в файле AndroidManifest и имеют структурированный жизненный цикл.

После запуска Activity проходит через ряд методов, которые обрабатываются ОС Android и для работы с которыми для программиста существует ряд вызовов упоминание которых будет уместно в рамках данной работы:

1. onCreate() - вызывается при создании или перезапуска Activity. Внутри метода настраивается интерфейс Activity. Инициализирует статические данные активности, связывают данные со списками и т. д. Связывает с необходимыми данными и ресурсами. Задаёт внешний вид Activity через метод setContentView().

2. onStart() - Вслед за onCreate() всегда происходит вызов метода onStart(), однако перед onStart() не обязательно должен идти вызов метода onCreate(), в связи с тем, что метод onStart() может вызываться и для возобновления работы приостановленного приложения.

3. onResume() вызывается вслед за onStart(). В этот момент пользователь взаимодействует с интерфейсом приложения.

4. onPause() - вызывается когда пользователь приступает к работе с новым окном, в таком случае, системой происходит обращение к методу onPause() для предшествующего окна. По своей сути происходит сворачивание Activity.

5. onStop() - метод вызывается, когда окно становится более невидимым для пользователя. Это может произойти, например, в случае уничтожении Activity.

6. onRestart() - Вызов данного метода происходит после того, как как Activity была остановлена и перезапущена пользователем.

7. onDestroy() - Метод вызывается по окончании работы. [9]

2.1.1 Стек Activity

Activity, которые открывал пользователь во время работы с мобильным приложением, находятся внутри сущности под названием Task.

При открытии новой Activity создается ее экземпляр и располагается системой наверху стека.

Далее, в момент, когда пользователь производит действие, при котором он переходит в предыдущую Activity (например, нажав кнопку «Назад»), Activity, которая была расположена наверху стека, уничтожается и отображается Activity, которая располагается в стеке под ней.

Activity, которые находятся внутри так называемого back stack могут относиться не только к исполняемому приложению, но и к любому другому приложению внутри системы.

Таким образом, при использовании функциональности стороннего приложения, в back stack помещается вызванная из этого приложения Activity, затем в случае, например, нажатия на кнопку “Назад” пользователь вернется к приложению, с которым происходило взаимодействие ранее.

Такое архитектурное решение сделано для того, чтобы создавалось ощущение работы с одним большим приложением.

Атрибут taskAffinity характеризует, к какому Task должна присоединиться Activity при запуске.

Это строка, которая определяется в AndroidManifest следующим образом:

<activity android:name=".MainActivity" android:taskAffinity="com.example.com">

Пример 5: taskAffinity.

Либо по умолчанию равняется идентификационному номеру приложения в системе. В случае, если явно прописывать значение taskAffinity в файле AndroidManifest, то можно заставить запускаться Activity в контексте произвольного приложения.

Схематично можно отобразить связь taskAffinity и Activity следующим образом:

Рис 2. Связь taskAffinity и Activity.

Помимо taskAffinity, следует упомянуть директиву allowTaskReparenting. Данный атрибут позволяет Activity приложения перемещаться из задачи (Task) которая их запустила в задачу переднего плана, с которой Activity имеют общее значение атрибута taskAffinity. [1]

2.1.2 Возможность запуска вредоносного приложения в контексте доверенного приложении

Уровень критичности: средний

Данная атака возможна в связи с тем, что любое приложение может использовать произвольное значение для атрибута taskAffinity.

Рассмотрим следующий пример:

Val loginButton = findViewById<Button>(R.id.loginButton)

loginButton.setOnClickListener {

val intent = Intent(this, AnotherActivity::class.java)

startActivity(intent)

}

}

Пример 6: loginButton.

Приложение c названием TestApp имеет функциональность открытия второго Activity (в примере - AnotherActivity) при нажатии на кнопку “Login”.

Таким образом после нажатия пользователем на кнопку “Login” наверху стека окажется AnotherActivity.

После закрытия AnotherActivity, наверху стека снова окажется MainActivity. И так далее. После закрытия Activity, на вершину стека будет выталкиваться предыдущее и т. д.

Данное поведение открывает следующую возможность для атаки. Предположим, злоумышленник написал вредоносное приложение, и установил следующее значение в AndroidManifest:

<activity android:name=".MainActivity"

android:taskAffinity="com.example.TestApp"

android:allowTaskReparenting="true">

Пример 7: allowTaskReparenting.

В данном случае вредоносное приложение устанавливает в качестве значение атрибута taskAffinity название атакуемого приложения.

Таким образом, если данное приложение окажется на устройстве пользователя (и будет запущено), то после того, как пользователь откроет TestApp и в последствии закроет его, нажав на кнопку назад, наверху стека окажется Activity вредоносного приложения.

Процесс обнаружения недостатка

Принимая во внимание, что недостаток в данном случае является следствием архитектурного решения платформы Android, то сложно выделить конкретный аспект приложения, который явно указывал бы, что приложение никак не пытается противодействовать данному недостатку.

Оптимальным способом является написание собственного мини-приложения, в AndroidManifest которого в качестве значения атрибута taskAffinity указать полное название атакуемого приложения.

Анализ критичности

Чем это может быть опасно? Вредоносное приложение может быть сделано по подобию атакуемого приложения, тем самым, если функциональность данного приложения подразумевают какую-то аутентификацию, вредоносное приложение может отрисовать подобный атакуемому приложению экран, с просьбой ввести учетные данные, тем самым, попытавшись их украсть.

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

Рекомендации по устранению

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

Одним из потенциальных способов устранения данного недостатка можно считать в добавлении атрибута taskAffinity для каждого Activity легитимного приложения с пустым значением.

Таким образом, каждый Activity будет запускаться в случайно сгенерированной задаче. [51]

2.1.3 Наличие флага android:exported для Activity со значением True

Уровень критичности: информационный

Следующим недостатком в рамках Activity можно считать использование атрибута android: exported со значением True.

Таким образом Activity позволяет другим приложениям на мобильном устройстве взаимодействовать с ним.

Процесс обнаружения недостатка

После декомпиляции приложения необходимо открыть файл AndroidManifest приложения и проанализировать его на наличие следующей строки:

1. android:exported="true"

В случае нахождения проверить код, соответствующих Activity на предмет потенциальных недостатков валидации и санитизации, а также оценить, насколько эти Activity способны раскрыть конфиденциальную информацию о пользователе.

Анализ критичности

Сам по себе данный недостаток не несет уязвимости, однако, все зависит от того, насколько это Activity фильтрует получаемые Intent'ы, а также насколько целесообразно открытие того или иного Activity. Отметим, что именно наличие данного флага открывает потенциальные вектора для атаки.

Рекомендации по устранению

При написании приложения рекомендуется тщательно обдумать и оценить потенциальные риски при использовании android: exported=“true”.

В случае, если это необходимо, рекомендуется реализовать функциональность валидации и санитизации получаемых данных.

2.2 Content Provider

Content provider - это компонент, который управляет доступом к центральному репозиторию данных приложения (например, локальной БД приложения).

Content Provider является компонентом приложения Android, который зачастую имеет собственный пользовательский интерфейс для работы с данными.

Content Provider рекомендуется применяется в тех случаях, когда вы хотите использовать данные совместно с другими приложениями, работающими на мобильном устройстве.

Для создание Content Provider необходимо наследоваться от абстрактного класса Content Provider, перезаписать следующие методы - onCreate(), query(), insert(), update(), delete(), getType(), а так же указать название провайдера в файле AndroidManifest.xml приложения, указав значение exported= “true”.

Чтобы получить данные из Content Provider или сохранить в нём новую информацию, приложению, которое намеривается это сделать, нужно использовать набор идентификаторов URI.

Например, если бы вам было нужно получить набор данных из Content Providera, в котором хранятся имена и оценки студентов, понадобился бы примерно такой URI (запрос к получению всех записей таблицы students):

content://com.android.hse.contentprovider/students

URL строится по следующему принципу:

1. Схема. Обязательно должна быть схема content://

2. Уникальный идентификатор провайдера. Прописывается в AndroidManifest.xml

3. Путь к конкретной таблице. [16]

2.2.1 Контроль доступа

Контроль доступа реализуется через использование атрибутов <permission> в файле AndroidManifest.xml приложения.

Используя android:permission можно указать разрешения, которые контролируют права на чтение и запись для всего поставщика.

С другой стороны, рекомендуется использовать раздельное определение разрешений на запись и чтение для каждого компонента поставщика данных. Так же это может быть более удобным в случае, если при использовании поставщика некоторые приложения могли только считывать информацию и некое множество других вносить информацию в локальную БД.

Права на запись контролируется атрибутом android:readPermission, в то время как права на чтение - атрибутом android:writePermission.

Существуют три разных атрибута, которым можно настроить те или иные права для доступа к конкретном подмножеству данных.

1. android:path="/hse" - распространяется на все записи внутри /hse но не распространятся на поддиректории /hse.

2. android:pathPrefix="/hse" - распространяются на все директории внутри /hse.

3. android:pathPattern - будет распространятся на все URL которые будут соответствовать указанному шаблону. Например android:pathPattern = "/.*/.*/.*/.*/..*"

К примеру, если требуется разграничить доступ для информации, которая находится по URL content://com.example.app/path1 и content://com.example.app/path2 то содержание файла “AndroidManifest” может быть следующим:

<provider …>

<path-permission android:pathPrefix="/path1"

android:readPermission="com.example.app.SUBPATH1_READ_PERMISSION"

android:writePermission="com.example.app.SUBPATH1_WRITE_PERMISSION" />

<path-permission android:pathPrefix="/path2"

android:writePermission="com.example.app.SUBPATH2_WRITE_PERMISSION" />

</provider>

Пример 8: path-permission.

В случае использования поставщика данных для внешних приложений, существует возможность реализации временных разрешений на запись или чтение.

Иными словами, приложение может выдавать разрешение на чтение какого-то внутреннего файлы и отзывать его через какое-то время.

Для этого можно воспользоваться атрибутом android:grantUriPermission="true" в элементе <provider>.

Однако, как и в случае выше, можно реализовать временные разрешения для отдельных URI, добавив дополнительный элемент <grant-uri-permission> в элемент <provider>.

И также, как и с <path-permission> можно использовать android:path, android:pathPrefix, android:pathPattern.

Таким образом, это может выглядеть вот так:

<provider>

<grant-uri-permission android:path="/hse" />

</provider>

Пример 9: grant-uri-permission.

В этом случае разрешение позволяет выдавать только данные, которые находятся по URL content://com.example.app/hse.

После формирования файла AndroidManifest приложения, следующим шагом является реализация функциональности выдачи временных разрешений в коде приложения.

Для этого необходимо реализовать объект Intent, указать какой флаг будет передаваться внутри объекта (FLAG_GRANT_READ_URI_PERMISSION или FLAG_GRANT_WRITE_URI_PERMISSION).

Далее объект Intent будет передан в качестве аргумента для метода startActivity() или startService().

Таким образом, в момент отправки объекта Intent сервиса или активности которое выдает разрешение, система Android проверит, может ли данное приложение выдавать временный доступ для данных по указанному URL и в случае успеха Intent будет доставлен указанному приложению.

В качестве примера можно рассмотреть следующий участок кода:

val url = Uri.parse("content://com.example.app/hse")

val intent = Intent("com.example.second.app")

intent.data = url

intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

Пример 10: FLAG_GRANT_READ_URI_PERMISSION.

Создается объект Intent направленный для приложения com.example.second.app, затем в качестве содержания для объекта Intent передается URL поставщика данных, доступ к которому мы хотим предоставить, далее, задается флаг GRANT_READ_URI_PERMISSION который дает доступ на чтение.

Для того, что бы отозвать доступ, необходимо вызвать метод revokeUriPermission(). [64]

Рассмотрим недостатки, которые могут встретиться при реализации поставщика услуг.

2.2.2 Отсутствие вызова revokeUriPermission() после выдачи разрешения стороннему приложению

Уровень критичности: информационный

Данный недостаток позволяет стороннему приложению получить длительный доступ к данным приложения, которое предоставило разрешение.

Приведем пример в каком случае это может произойти:

1. Пользователю приложения А необходимо обработать изображение.

2. Приложение А создает Intent с соответствующими флагами и отправляет его приложению Б, которое имеет функциональность обработки изображений.

3. Приложение Б получает доступ к этому изображению и возможно ко всем изображениям поставщика услуг.

В данном примере, приложение Б будет иметь доступ ко всем изображениям приложения А т.к. не было отозвано разрешение через использование revokeUriPermission().

Процесс обнаружения недостатка

Данный недостаток достаточно легко обнаружить при анализе исходного кода мобильного приложения.

Необходимо найти всех поставщиком данных приложения и проследить наличие или отсутствие вызова метода revokeUriPermission().

Анализ критичности

Данный недостаток не является уязвимостью сам по себе, однако противоречит рекомендуемым практикам при разработке приложений. [64]

Рекомендации по устранению

Рекомендуется использовать метод revokeUriPermission() в каждом случае предоставления временного разрешения к информации.


Подобные документы

  • Общие характеристики операционной системы Android. Разработка приложения на основе создания менеджера файлов. Получение с помощью приложения доступа к файлам, хранящимся в "облачном хранилище" в сети Интернет. Расчет стоимости программного обеспечения.

    дипломная работа [2,7 M], добавлен 03.04.2015

  • Архитектура операционной системы Android, набор библиотек для обеспечения базового функционала приложений и виртуальная машина Dalvik. Объектно-ориентированный язык программирования Java как инструмент разработки мобильных приложений для ОС Android.

    дипломная работа [1,6 M], добавлен 08.07.2015

  • Архитектура и история создания операционной системы Android. Язык программирования Java. Выбор средства для реализации Android приложения. Программная реализация Android приложения. Проведение тестирования разработанного программного обеспечения.

    курсовая работа [167,8 K], добавлен 18.01.2017

  • Разработка программного обеспечения для платформы Android версии 2.3: информационное приложения для поклонников футбольной команды, с возможностью просмотра событий, статистики и иной информации о команде и ее успехах. Листинг JsonDataManager.java.

    дипломная работа [4,1 M], добавлен 24.04.2013

  • Преимущества операционной системы Android. Проектирование интерфейса приложений. Визуальные редакторы и средства кроссплатформенной разработки. Оптимизация игрового процесса, выбор фреймворка и библиотек. Классификация и характеристика игр по жанрам.

    дипломная работа [2,6 M], добавлен 10.07.2017

  • Современное состояние рынка мобильных приложений. Основные подходы к разработке мобильных приложений. Обоснование выбора целевой группы потребителей приложения. Этапы проектирования и разработки мобильного приложения для операционной системы Android.

    курсовая работа [987,1 K], добавлен 27.06.2019

  • Определение базы данных и банков данных. Компоненты банка данных. Основные требования к технологии интегрированного хранения и обработки данных. Система управления и модели организации доступа к базам данных. Разработка приложений и администрирование.

    презентация [17,1 K], добавлен 19.08.2013

  • Характеристика работы операционной системы Android, используемой для мобильных телефонов. Создание Android проекта в среда разработки Eclipse. Общая структура и функции файла манифест. Компоненты Android приложения. Способы осуществления разметки.

    курсовая работа [1,0 M], добавлен 15.11.2012

  • Современные базы данных – многофункциональные программные системы, работающие в открытой распределенной среде изучении администрирования базы данных. Способы организации внешней памяти баз данных. Системы управления базами данных для хранения информации.

    курсовая работа [185,6 K], добавлен 07.12.2010

  • Проведение системного анализа предметной области и разработка проекта по созданию базы данных для хранения информации о перевозках пассажиров и грузов. Обоснование выбора системы управления базой данных и разработка прикладного программного обеспечения.

    курсовая работа [1,1 M], добавлен 18.07.2014

Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
PPT, PPTX и PDF-файлы представлены только в архивах.
Рекомендуем скачать работу.