1 авг 2020 ZloVred333415 :
TWRP, как отдельная операционная система. Структура, принципы работы, основные составляющие.
Что являет собой TWRP?
статья
В общих чертах о процессе создания.
статья
Разбираем, изучаем и собираем обратно образ TWRP.
статья
"Учим" TWRP расшифрововать пользовательские данные.
информация
О DM-verity + вариант его отключения при помощи патча TWRP
общая информация
Материалы взяты из открытых источников
статья
TWRP, он же "кастомный рекавери", является заменой стандартному средству восстановления, идущему, как правило, в составе заводской прошивки аппарата. Основной его функцией изначально было создание и восстановление бэкапа существующей операционной системы как полностью, так и по отдельным разделам. Также была реализована возможность установки неподписанных пакетов обновления (в народе "патчи"), что дало возможность любому пользователю создавать свои пакеты и, таким образом, вносить практически любые правки в операционную систему.
По структуре - это всё тот же boot, но самостоятельный, независимый* от наличия, версии и состояния установленной ОС. Т.е. имеется своё ядро, а связующий бинарник "init" в составе TWRP не пытается загрузить Android, находящийся в системном разделе, а грузит собственную мини-ОС. В ней находится полный набор необходимых для запуска файлов и инструкций, которые тоже можно редактировать, удалять, добавлять и обновлять, меняя тем самым функционал сборки. Ничего не напомнило?
Вот именно в таком ключе и пойдёт речь о TWRP в этой теме! Это не только удобный инструмент! Это - операционная система, которую мы будем изучать и настраивать под свои нужды.
Для расшифровки userdata бывает необходимо знать, какой Security Patch Level прошивки, и иногда также иметь блобы для работы qseecomd.
А иначе - не расшифруется.
По структуре - это всё тот же boot, но самостоятельный, независимый* от наличия, версии и состояния установленной ОС. Т.е. имеется своё ядро, а связующий бинарник "init" в составе TWRP не пытается загрузить Android, находящийся в системном разделе, а грузит собственную мини-ОС. В ней находится полный набор необходимых для запуска файлов и инструкций, которые тоже можно редактировать, удалять, добавлять и обновлять, меняя тем самым функционал сборки. Ничего не напомнило?
Вот именно в таком ключе и пойдёт речь о TWRP в этой теме! Это не только удобный инструмент! Это - операционная система, которую мы будем изучать и настраивать под свои нужды.
Для расшифровки userdata бывает необходимо знать, какой Security Patch Level прошивки, и иногда также иметь блобы для работы qseecomd.
А иначе - не расшифруется.
В общих чертах о процессе создания.
статья
Кастомный рекавери, как уже упоминалось ранее, это "доработанная" версия стандартного (идущего "из коробки"). Имеет больше возможностей, в меньшей мере ограничивает пользователя и может быть более тонко настроено (зачастую стоковое вообще не имеет настроек).
Дак как же нам заполучить это "чудо" для нашего девайса? В основном используется три способа*:
1. Создание из исходников. Если производитель Вашего устройства был так любезен, что не поленился выложить в открытый доступ исходный код программного обеспечения, которое там используется, то можно "наложить" на него исходники кастомного рекавери (вот с этим то проблем нету). Вот мы и получаем свой, родной TWRP, CWM и т.д. Собранное таким образом кастомное рекавери может получить статус официального, а девайс, для которого оно сделано, может получить статус официально поддерживаемого проэктом.
2. Портирование. Вам не так повезло? Производитель не удосужился опубликовать исходный код ПО к своему девайсу? Не будем отчаиваться - выход есть! Портирование!
Нам понадобится аппарат, который максимально подобен нашему "основой" ("материнка", board или как сейчас "по-научному"), но имеет собранный и рабочий кастомный рекавери. При нынешнем разнообразии рынка устройств под управлением Android OS - это вообще не проблема! Теперь разберём стоковый и кастомный рекавери (который от чужого аппарата), заменив ядро второго упомянутого ядром от нашего девайса. Собираем обратно. Всё! В основном, если был правильно подобран "донор", этого достаточно для успешного запуска. Пользоваться в таком виде не получится, но отладить пару погрешностей - это не компилировать "с нуля", не имея исходников. В чём основной минус такого метода? Отсутствие официальной поддержки! Т.е. нужно следить за изменением в структуре прошивок и самолично вносить соответствующие изменения в свой "порт".
*способы описываю в общих чертах, вскользь и исключительно для общего понимания.
Дак как же нам заполучить это "чудо" для нашего девайса? В основном используется три способа*:
1. Создание из исходников. Если производитель Вашего устройства был так любезен, что не поленился выложить в открытый доступ исходный код программного обеспечения, которое там используется, то можно "наложить" на него исходники кастомного рекавери (вот с этим то проблем нету). Вот мы и получаем свой, родной TWRP, CWM и т.д. Собранное таким образом кастомное рекавери может получить статус официального, а девайс, для которого оно сделано, может получить статус официально поддерживаемого проэктом.
2. Портирование. Вам не так повезло? Производитель не удосужился опубликовать исходный код ПО к своему девайсу? Не будем отчаиваться - выход есть! Портирование!
Нам понадобится аппарат, который максимально подобен нашему "основой" ("материнка", board или как сейчас "по-научному"), но имеет собранный и рабочий кастомный рекавери. При нынешнем разнообразии рынка устройств под управлением Android OS - это вообще не проблема! Теперь разберём стоковый и кастомный рекавери (который от чужого аппарата), заменив ядро второго упомянутого ядром от нашего девайса. Собираем обратно. Всё! В основном, если был правильно подобран "донор", этого достаточно для успешного запуска. Пользоваться в таком виде не получится, но отладить пару погрешностей - это не компилировать "с нуля", не имея исходников. В чём основной минус такого метода? Отсутствие официальной поддержки! Т.е. нужно следить за изменением в структуре прошивок и самолично вносить соответствующие изменения в свой "порт".
*способы описываю в общих чертах, вскользь и исключительно для общего понимания.
Разбираем, изучаем и собираем обратно образ TWRP.
статья
Пришло время заглянуть внутрь этой операционной системы, увидеть её состовляющие и узнать "кто за что здесь отвечает". Здесь и далее в теме все примеры распаковки/запаковки образов boot и recovery будут приведены с использованием Android Image Kitchen
Начинаем изучение содержимого TWRP. Возьмём интересующий нас образ и распакуем его. Увидим, что появилось две новые папки: "ramdisk" и "split_img".
коротко о "split_img"
пример работы с split_img на предмет отключения SELinux
коротко о содержимом "ramdisk"
Начинаем изучение содержимого TWRP. Возьмём интересующий нас образ и распакуем его. Увидим, что появилось две новые папки: "ramdisk" и "split_img".
коротко о "split_img"
Для упрощения и согласования информации, изложенной далее, предположим, что образ имеет название "twrp.img". Коротко о содержимом "split_img":
Здесь расположены примерно* следующие файлы:
twrp.img-base
twrp.img-board
twrp.img-cmdline
twrp.img-hash
twrp.img-headerversion
twrp.img-imgtype
twrp.img-kerneloff
twrp.img-oslevel
twrp.img-osversion
twrp.img-pagesize
twrp.img-ramdisk.cpio.gz **
twrp.img-ramdiskcomp
twrp.img-ramdiskoff
twrp.img-secondoff
twrp.img-tagsoff
twrp.img-zImage
Для чего эти файлы:
-base - сообщает базовый offset (пример содержимого "80000000").
-board - сообщает целевую платформу (пример содержимого "msm8937"), может быть пустым.
-cmdline - сообщает некоторые команды запуска ядра: платформу (androidboot.hardware, состояние SElinux, которое будет использовано при запуске (androidboot.selinux, тип сборки (buildvariant и т.д..
-hash - сообщает метод хеширования (пример содержимого "sha1").
-headerversion - сообщает версию заголовка (пример содержимого "0").
-imgtype - сообщает формат образа (пример содержимого "AOSP").
-kerneloff - сообщает offset ядра (пример содержимого "00008000").
-oslevel - сообщает Security Patch Level (пример содержимого "2019-07").
И это очень важно - если эту "дату" изменить - можно опять же нарваться на проблему расшифровки userdata
-osversion - сообщает целевую версию Android OS (пример содержимого "9.0.0").
-pagesize - сообщает размер одного блока (пример содержимого "4096").
-ramdisk.cpio.gz - содержит ramdisk распакованного образа в сжатом виде.
-ramdiskcomp - сообщает ифнормацию о формате, в котором упакован ramdisk*** (пример содержимого "gzip").
-ramdiskoff - сообщает offset содержащегося ramdisk (пример содержимого "01000000").
-secondoff - сообщает offset (чего конкретно не разобрался, пример содержимого "00f00000").
-tagsoff - сообщает offset atags (пример содержимого "00000100").
-zImage - ядро****.
*Т.к. структура образа boot/recovery вариируется в зависимости от марки, модели, платформы, взглядов на жизнь производителя и текущего положения галактик в отношении "синего карлика", то нужно считать пример обобщённым и учитывать возможные отличия.
**twrp.img-ramdisk.cpio.gz - это пример распакованной части образа, содержащей "ramdisk". Почему пример? Потому, что это архив в чистом виде. Расширение ".gz" будет присутствовать только в том случае, если "ramdisk" сжат в формате gzip. Но могут быть и другие! Неполный перечень в виде "расширение - формат":
gz - gzip
lzo - lzop
xz - xz
lzma - lzma (иногда тоже xz)
bz2 - bzip2
lz4 - lz4
***Это очень важный момент и мы будем активно и охотно его использовать. Для того, чтобы собранный нами TWRP помещался в раздел /recovery телефона, он должен подходить по размеру. Но мы ведь хотим "запихнуть" в нашу сборку всего-привсего и много-примного! Чтобы уместить всё "добро" будем плотнее сжимать ramdisk, изменяя формат сжатия. gzip не сильно нам поможет сделать сборку компактной - лучше использовать lzma. Вот для этого изменяем в файле "twrp.img-ramdiskcomp" содержащееся там "gzip" (к примеру) на "lzma". Всё! Значение заменяем только раз. С этого момента и далее наша сборка будет сжиматься гораздо плотнее. Но! Для использования этого способа сжатия оно должно поддерживаться ядром!
****TWRP способно использовать для работы практически любое ядро, собранное для Вашего устройства. Заменить его на более актуальное иногда полезно, а иногда и просто необходимо! Для поддержки упомянутого ранее сжатия ramdick в lzma может потребоваться взять zImage от boot из свежей кастомной прошивки или из сборки TWRP, в которой поддержка уже реализована. Также версия ядра влияет на то, способно ли будет наше кастомное рекавери расшифрововать для работы раздел /data. Запомните этот момент!
Здесь расположены примерно* следующие файлы:
twrp.img-base
twrp.img-board
twrp.img-cmdline
twrp.img-hash
twrp.img-headerversion
twrp.img-imgtype
twrp.img-kerneloff
twrp.img-oslevel
twrp.img-osversion
twrp.img-pagesize
twrp.img-ramdisk.cpio.gz **
twrp.img-ramdiskcomp
twrp.img-ramdiskoff
twrp.img-secondoff
twrp.img-tagsoff
twrp.img-zImage
Для чего эти файлы:
-base - сообщает базовый offset (пример содержимого "80000000").
-board - сообщает целевую платформу (пример содержимого "msm8937"), может быть пустым.
-cmdline - сообщает некоторые команды запуска ядра: платформу (androidboot.hardware, состояние SElinux, которое будет использовано при запуске (androidboot.selinux, тип сборки (buildvariant и т.д..
-hash - сообщает метод хеширования (пример содержимого "sha1").
-headerversion - сообщает версию заголовка (пример содержимого "0").
-imgtype - сообщает формат образа (пример содержимого "AOSP").
-kerneloff - сообщает offset ядра (пример содержимого "00008000").
-oslevel - сообщает Security Patch Level (пример содержимого "2019-07").
И это очень важно - если эту "дату" изменить - можно опять же нарваться на проблему расшифровки userdata
-osversion - сообщает целевую версию Android OS (пример содержимого "9.0.0").
-pagesize - сообщает размер одного блока (пример содержимого "4096").
-ramdisk.cpio.gz - содержит ramdisk распакованного образа в сжатом виде.
-ramdiskcomp - сообщает ифнормацию о формате, в котором упакован ramdisk*** (пример содержимого "gzip").
-ramdiskoff - сообщает offset содержащегося ramdisk (пример содержимого "01000000").
-secondoff - сообщает offset (чего конкретно не разобрался, пример содержимого "00f00000").
-tagsoff - сообщает offset atags (пример содержимого "00000100").
-zImage - ядро****.
*Т.к. структура образа boot/recovery вариируется в зависимости от марки, модели, платформы, взглядов на жизнь производителя и текущего положения галактик в отношении "синего карлика", то нужно считать пример обобщённым и учитывать возможные отличия.
**twrp.img-ramdisk.cpio.gz - это пример распакованной части образа, содержащей "ramdisk". Почему пример? Потому, что это архив в чистом виде. Расширение ".gz" будет присутствовать только в том случае, если "ramdisk" сжат в формате gzip. Но могут быть и другие! Неполный перечень в виде "расширение - формат":
gz - gzip
lzo - lzop
xz - xz
lzma - lzma (иногда тоже xz)
bz2 - bzip2
lz4 - lz4
***Это очень важный момент и мы будем активно и охотно его использовать. Для того, чтобы собранный нами TWRP помещался в раздел /recovery телефона, он должен подходить по размеру. Но мы ведь хотим "запихнуть" в нашу сборку всего-привсего и много-примного! Чтобы уместить всё "добро" будем плотнее сжимать ramdisk, изменяя формат сжатия. gzip не сильно нам поможет сделать сборку компактной - лучше использовать lzma. Вот для этого изменяем в файле "twrp.img-ramdiskcomp" содержащееся там "gzip" (к примеру) на "lzma". Всё! Значение заменяем только раз. С этого момента и далее наша сборка будет сжиматься гораздо плотнее. Но! Для использования этого способа сжатия оно должно поддерживаться ядром!
****TWRP способно использовать для работы практически любое ядро, собранное для Вашего устройства. Заменить его на более актуальное иногда полезно, а иногда и просто необходимо! Для поддержки упомянутого ранее сжатия ramdick в lzma может потребоваться взять zImage от boot из свежей кастомной прошивки или из сборки TWRP, в которой поддержка уже реализована. Также версия ядра влияет на то, способно ли будет наше кастомное рекавери расшифрововать для работы раздел /data. Запомните этот момент!
пример работы с split_img на предмет отключения SELinux
Немного о том, что такое SELinux, как с ним жить, и нужно ли? Android, который собран на костяке Linux, в принципе не так уж плохо защищён. Не имея прав суперпользователя мы мало чем можем повлиять не работу операционной системы. Но получив эти самые права неопытный юзер может натворить делов. Да ещё и этот собранный "на коленке" софт... SELinux как раз то и является "последним рубежом" в защите системы от подозрительных и потенциально вредоносных действий. Он препятствует манипуляциям в некоторых особо значимых областях прошивки даже процессам, выполняемым от имени root пользователя. В основном это скорее хорошо, чем плохо . Но это мешает "веселью"! Для применения множества полезных твиков SELinux нужно отключать. Отключаем SELinux в boot! Сложный, но надёжный вариант - разберём ядро и поправим коммандную строку "ручками":
Качаем "Android Image Kitchen" (есть в этом посте). Извлекаем содержимое архива куда удобно. "Ложим" наш целевой или boot.img в корень папки "Android Image Kitchen". ВНИМАНИЕ! Бут должен быть именно от той прошивки, для которой собрались отключать SELinux! Запускаем "unpackimg.bat". Завершиться распаковка за пару секунд, жмём строго на клавишу "any key". Заходим в папку "split_img" и открываем с помощью Notepad++ файл "boot.img-cmdline". Видим длиннючую строчку типа
Качаем "Android Image Kitchen" (есть в этом посте). Извлекаем содержимое архива куда удобно. "Ложим" наш целевой или boot.img в корень папки "Android Image Kitchen". ВНИМАНИЕ! Бут должен быть именно от той прошивки, для которой собрались отключать SELinux! Запускаем "unpackimg.bat". Завершиться распаковка за пару секунд, жмём строго на клавишу "any key". Заходим в папку "split_img" и открываем с помощью Notepad++ файл "boot.img-cmdline". Видим длиннючую строчку типа
console=tty60,115200,n8 androidboot.console=tty60 androidboot.hardware=qcom msm_rtb.filter=0x237 ehci-hcd.park=3 lpm_levels.sleep_disabled=1 gpt androidboot.bootdevice=7824900.sdhci earlycon=msm_hsl_uart,0x78B0000 androidboot.selinux=enforcing buildvariant=userdebug
Находим и заменяем ОДНО СЛОВОenforcing
наpermissive
Не добавляя ни пробелов, ни других символов! Сохраняем файл. Возвращаемся в корень "Android Image Kitchen" и запускаем "repackimg.bat". Завершиться перепаковка за пару секунд, снова жмём строго на клавишу "any key". Готово! Результирующий файл "image-new.img" "обзываем" как угодно и прошиваем!коротко о содержимом "ramdisk"
Взглянув на содержимое папки "ramdik", полученной в процессе распаковки TWRP, сразу можно заметить немалое сходство с содержимым ramdisk'а бута. Почему так? Всё просто! Рекавери - это структурно тот же бут (о принципиальных отличиях упомянул ранее). Директории и симлинки, содержащиеся там, являются точками монтирования системных разделов и каталогов. Также в некоторых из них содержаться утилиты, драйвера и инструкции для запуска TWRP и работы с разделами накопителя и/или файлами, там содержащимися. К этому вернёмся позже. Сейчас в общих чертах рассмотрим некоторые файлы*, содержащиеся в корне "ramdisk":
init - исполняемый файл (далее позволю себе называть его и ему подобные "бинарниками"). Первоначальный сервис, который имеет руд-доступ по умолчанию и может предоставлять его дочерним сервисам и процессам. Основная функция - запуск ОС (в нашем конкретном случае это TWRP). В процессе выполнения следует сценариям запуска, которые представлены в взаимоподгружаемых** файлах с расширением ".rc".
файлы "*.rc" - сценарии последовательности загрузки ОС, в которых используется собственный язык "init" (подробное описание этого языка можно прочитать по ссылке в шапке). Содержимое их разнородно: от банального создания папки до запуска целого дерева сервисов. Изменением их содержимого можно добиться примерно (или точно) того же, чего и правкой этих файлов в boot***.
Сервисы - это отдельная сущность, они не только по триггерам стартуют, а ещё и через прямое указание (типа start recovery в init*.rc) или же вручную через start recovery в root shell.
Точнее, сервисы стартуют (кроме тех что disabled) по class-start командам в init*.rc. Те сервисы что disabled не стартуют по class-start, но стартуют по прямой команде и далее работают как прописано. То есть, если в сервисе прописано class core то он стартанёт по class-start core.
https://android.googlesource.com/platform/system/core/+/master/init/README.md
файлы "*_contexts" - определяют разрешения файлов, и их групп, директорий и, местами, целых разделов в контексте SElinux. Сообщается группа, к которой принадлежит объект, группы пользователей, способных обращаться к объекту и в каких целях этим пользователям дозволено обращаться к объекту.
prop.default**** - подобие всем известного build.prop из раздела /system или /vendor. В TWRP используется в таких же целях - конфигурирование некоторых аспектов работы ОС (настройки из такого же файла в буте переопределяются на настройки из build.prop в процессе загрузки).
символические ссылки (симлинки) - ссылаются на объекты, находящиеся вне корня рамдиска, но необходимые там для обратной совместимости (или в случае non-treble прошивок симлинк vendor, ссылающийся на /system/vendor).
*Т.к. структура образа boot/recovery вариируется в зависимости от марки, модели, платформы, взглядов на жизнь производителя и текущего положения галактик в отношении "синего карлика", то нужно считать пример обобщённым и учитывать возможные отличия.
**Под взаимоподгружаемостью подразумевается возможность в рамках оного такого файла вызвать обработку ещё одного или даже нескольких таких сценариев командой
****иногда default.prop или симлинк с названием "default.prop", указывающий на "prop.default". А подправив содержимое этого файла можно добиться, к примеру, активации/деактивации в сборке adb по умолчанию
init - исполняемый файл (далее позволю себе называть его и ему подобные "бинарниками"). Первоначальный сервис, который имеет руд-доступ по умолчанию и может предоставлять его дочерним сервисам и процессам. Основная функция - запуск ОС (в нашем конкретном случае это TWRP). В процессе выполнения следует сценариям запуска, которые представлены в взаимоподгружаемых** файлах с расширением ".rc".
файлы "*.rc" - сценарии последовательности загрузки ОС, в которых используется собственный язык "init" (подробное описание этого языка можно прочитать по ссылке в шапке). Содержимое их разнородно: от банального создания папки до запуска целого дерева сервисов. Изменением их содержимого можно добиться примерно (или точно) того же, чего и правкой этих файлов в boot***.
Сервисы - это отдельная сущность, они не только по триггерам стартуют, а ещё и через прямое указание (типа start recovery в init*.rc) или же вручную через start recovery в root shell.
Точнее, сервисы стартуют (кроме тех что disabled) по class-start командам в init*.rc. Те сервисы что disabled не стартуют по class-start, но стартуют по прямой команде и далее работают как прописано. То есть, если в сервисе прописано class core то он стартанёт по class-start core.
https://android.googlesource.com/platform/system/core/+/master/init/README.md
файлы "*_contexts" - определяют разрешения файлов, и их групп, директорий и, местами, целых разделов в контексте SElinux. Сообщается группа, к которой принадлежит объект, группы пользователей, способных обращаться к объекту и в каких целях этим пользователям дозволено обращаться к объекту.
prop.default**** - подобие всем известного build.prop из раздела /system или /vendor. В TWRP используется в таких же целях - конфигурирование некоторых аспектов работы ОС (настройки из такого же файла в буте переопределяются на настройки из build.prop в процессе загрузки).
символические ссылки (симлинки) - ссылаются на объекты, находящиеся вне корня рамдиска, но необходимые там для обратной совместимости (или в случае non-treble прошивок симлинк vendor, ссылающийся на /system/vendor).
*Т.к. структура образа boot/recovery вариируется в зависимости от марки, модели, платформы, взглядов на жизнь производителя и текущего положения галактик в отношении "синего карлика", то нужно считать пример обобщённым и учитывать возможные отличия.
**Под взаимоподгружаемостью подразумевается возможность в рамках оного такого файла вызвать обработку ещё одного или даже нескольких таких сценариев командой
import /<название_подгружаемого_сценария>
***Напомню, что именно внесением правок в подобные файлы бута можно добится поддержки устройством init.d, модификаций звука, рут-доступа и т.д..****иногда default.prop или симлинк с названием "default.prop", указывающий на "prop.default". А подправив содержимое этого файла можно добиться, к примеру, активации/деактивации в сборке adb по умолчанию
ro.debuggable=1
persist.sys.usb.config=mtp,adb
"Учим" TWRP расшифрововать пользовательские данные.
информация
Скупая (уж как получилось) информация о том, как заставил научил портируемый TWRP расшифрововать Data:
-Разобрал boot от используемой прошивки, которая и зашифровала раздел.
-Разобрал портируемый TWRP.
-Заменил файлы из директории split_img из TWRP файлами из директории split_img от boot (кроме единственного - сжатого ramdisk (его не заменял - это сама кухня при надобности устроит).
-Собрал.
-Заработало.
-Разобрал boot от используемой прошивки, которая и зашифровала раздел.
-Разобрал портируемый TWRP.
-Заменил файлы из директории split_img из TWRP файлами из директории split_img от boot (кроме единственного - сжатого ramdisk (его не заменял - это сама кухня при надобности устроит).
-Собрал.
-Заработало.
О DM-verity + вариант его отключения при помощи патча TWRP
общая информация
В список общих статей о шифровании/дешифровании в Андроид можно добавить ссылки на:
Universal DM-Verity, ForceEncrypt, Disk Quota Disablers
XDA
Что такое dm-verity:
*----------------------------------------------------------------------------*
Android 4.4 и выше поддерживает Verified Boot с помощью дополнительной функции ядра device-mapper-verity (dm-verity), которая обеспечивает прозрачную проверку целостности блочных устройств.
Эта функция помогает пользователям Android быть уверенными, что при загрузке устройство находится в том же состоянии, в котором оно использовалось в последний раз.
source.android.com
Universal DM-Verity, ForceEncrypt, Disk Quota Disablers
XDA
Что такое dm-verity:
*----------------------------------------------------------------------------*
Android 4.4 и выше поддерживает Verified Boot с помощью дополнительной функции ядра device-mapper-verity (dm-verity), которая обеспечивает прозрачную проверку целостности блочных устройств.
Эта функция помогает пользователям Android быть уверенными, что при загрузке устройство находится в том же состоянии, в котором оно использовалось в последний раз.
source.android.com
Материалы взяты из открытых источников
Сообщество: Android Hack
Комментарии (9)
Показать комментарий
Скрыть комментарий
Для добавления комментариев необходимо авторизоваться
Интерны
Увлекательная игра в больничку