مبانی معماری امنیت اندروید – بخش دوم
مقاله
  • ۲۴ شهریور ۱۴۰۱
  • Learning Road Map
  • ۱۴ دقیقه خواندن

مبانی معماری امنیت اندروید – بخش دوم

آکادمی راوین

برای مطالعه‌ی بخش اول این مقاله به اینجا مراجعه کنید.

بخش ششم – کتابخانه‌های فریم‌ورک اندروید (Android Framework Libraries)

لایه‌ی بعدی استک ما در معماری اندروید، کتابخانه‌های فریم‌ورک اندروید است. فریم‌ورک اندروید شامل همه‌ی کتابخانه‌های جاوا به جز کتابخانه‌های runtime اندروید است که بخش مهمی از آن مختص پکیج‌های لایه بالای اندروید می‌باشد. در این فریم‌ورک‌ اجزای مورد نیاز برای ساخت اپلیکیشن‌های اندرویدی تعبیه شده است. به عنوان مثال می‌توان به کلاس‌های پایه برای activityها، serviceها و content providerها که با android.view.something و یا android.widget شناخته می‌شوند، اشاره کرد. فریم‌ورک اندروید همچنین شامل کلاس‌هایی برای فایل‌ها و دسترسی به دیتابیس است که بیشتر آن‌ها با پکیج‌های android.database.something و یا android.content.something وجود دارند. کلاس‌هایی نیز برای تعامل با سخت‌افزار وجود دارند. با این‌که همه‌ی کارکرد (functionality) سیستم‌عامل اندروید روی سطح کرنل، به عنوان سرویس‌های سیستمی پیاده‌سازی شده است، اما به صورت مستقیم در فریم‌ورک قابل دسترسی نیست. در واقع این دسترسی از طریق برخی کلاس‌های خارجی به نام manager امکان‌پذیر است. به صورت معمول، هر manager توسط یک سرویس سیستمی پشتیبانی می‌شود. به عنوان مثال، BluetoothManager یک نمای خارجی برای BluetoothManagerService است.

بخش هفتم – اپلیکیشن‌ها (Applications)

اپلیکیشن‌ها، به عنوان برنامه‌هایی که کاربرها به صورت مستقیم با آن‌ها در تعامل هستند، روی بالاترین سطح استک قرار دارند. با وجود این که همه‌ی اپلیکیشن‌ها ساختار یکسانی دارند و در قالب اندروید ساخته می‌شوند، اپلیکیشن‌ها به دو دسته‌ی اپلیکیشن‌های سیستمی و اپلیکیشن‌های نصب شده توسط کاربر تقسیم می شود که در ادامه شرح داده شده‌اند.

اپلیکیشن‌های سیستمی

اپلیکیشن‌های سیستمی به صورت پیش‌‎فرض در image مربوط به سیستم‌عامل وجود دارند. این اپلیکیشن‌ها Read-Only هستند و به صورت معمول در دایرکتوری /system قابل مشاهده خواهد بود. همچنین این اپلیکیشن‌ها قابل حذف شدن نیستند و توسط کاربران امکان تغییر آن‌ها وجود ندارد. این اپلیکیشن‌ها ایمن فرض شده و به آن‌ها دسترسی‌های بسیار بیشتری نسبت به اپلیکیشن‌های نصب شده توسط کاربر، می‌دهند.
اپلیکیشن‌های سیستمی می‌توانند عضو اصلی سیستم‌عامل اندروید باشند یا می‌توانند به عنوان یک اپلیکیشن ساده از پیش نصب شده باشند. مانند email clients یا browser. در حالی که همه‌ی اپلیکیشن‌ها در پوشه‌ی /system نصب می‌شوند، اما در اندروید‌های جدیدتر، اپلیکیشن‌های سیستمی در پوشه‌ی /system/priv-app/ قرار دارند که به این پوشه سطح دسترسی بالاتری نیز داده می‌شود. اپلیکیشن‌هایی که توسط کلید مخصوص پلتفرم امضا می‌شوند، می‌توانند مجوزهای سیستمی را داشته باشند که در این مکانیزم به وسیله‌ی یک امضای مشخص محافظت خواهند شد. در این حالت، این اپلیکیشن‌ها حتی می‌توانند دسترسی‌های مخصوص سطح سیستم‌عامل را نیز دریافت کنند.
از طرفی اپلیکیشن‌هایی که به صورت پیش‌فرض نصب هستند و قابل حذف شدن و یا تغییر دادن نیستند نیز می‌توانند به وسیله‌ی اجازه‌ای که کاربر به آن‌ها می‌دهد، update شوند. عملیات update زمانی انجام می‌شود که آن اپلیکیشن‌ها با یک کلید خصوصی مشترک امضا شده باشند تا اپلیکیشن‌های موجود در دستگاه بتوانند آن امضا را تایید کنند. البته بعضی از اپلیکیشن‌ها قابلیت override دارند. به عنوان مثال، کاربر این امکان را دارد که application launcher جداگانه‌ای را از طریق third-party applications نصب کند.

اپلیکیشن‌های نصب شده توسط کاربر (user-installed apps)

تمام اپلیکیشن‌هایی که کاربر نصب می­کند، بر روی یک پارتیشن از نوع read-write است. این پارتیشن /data نام دارد. این پوشه حاوی اطلاعات کاربر است که در زمان uninstall اپلیکیشن، اطلاعات مربوطه پاک خواهد شد. هر اپلیکیشن در یک sandbox امنیتی اختصاصی اجرا و باقی می‌ماند. به این معنی که به اطلاعات اپلیکیشن‌های دیگر دسترسی ندارد. بحث­‌هایی مانند Privilege Separation و Least Privilege نیز در این بخش مطرح هستند.

کامپوننت‌های مختلف اپلکیشن‌های اندرویدی

یک اپلیکیشن اندرویدی ترکیبی از کامپوننت‌­های مختلف است و برخلاف اپلیکیشن‌­های سنتی، می­تواند بیش از یک Entry Point داشته باشند. هر کامپوننت می­‌تواند entry point دل‌خواه خود را ارایه دهد که توسط اعمالی که کاربر انجام می­‌دهد، اپلیکیشن‌­های دیگر و یا توسط یک event سیستمی، فراخوانی خواهد شد. کامپوننت­‌ها و entry pointهای آن‌ها، مانند metadata های سنتی، در فایل manifest آن اپلیکیشن تعریف شده‌­اند که به آن AndroidManifest.xml می­‌گویند.
مانند همه فایل­‌های اصلی اندروید، استفاده از یک فایل کمکی که اینجا از نوع xml می­‌باشد، به کم حجم شدن فایل‌های APK کمک می‌­کند تا parse کردن آن نیز با سرعت بالاتری انجام شود. شاید مهم‌ترین بخشی که در فایل android manifest وجود دارد، package name است که سیستم‌عامل، اپلیکیشن را با این نام می­‌شناسد(همان com.google.email یعنی برعکس domain name). در واقع فایل android-manifest در زمان نصب اپلیکیشن، parse شده و packageها و componentها در سیستم‌عامل ثبت می‌­شوند. سیستم‌عامل اندروید این پیش­‌نیاز را برای developerها گذاشته که هر اپلیکیشن توسط developer مربوطه sign یا امضای اختصاصی داشته باشد. این روی‌کرد تضمین می‌کند تا آن اپلیکیشنی که نصب شده است، توسط اپلیکیشن دیگری replace نشود و کلید آن، حتی در صورت به‌روزرسانی، با کلیدی که در سرور ذخیره شده، تفاوتی نکند. به این موضوع در مقاله‌های آینده پرداخته خواهد شد. به طور کلی کامپوننت­های اپلیکیشن‌­های اندرویدی به شرح زیر است:

Activities

activity مانند یک صفحه، همان interface کاربر می‌باشد. در حقیقت activityها بلوک­‌های اصلی ساخت GUI هستند. اپلیکیشن‌ها activityهای مختلفی دارند که هر کدام می‌تواند با بقیه متفاوت باشد و در یک قالب جدا به کاربر نشان داده شود. نکته‌ی مهم در این‌جا این است که activityها می‌توانند به صورت جداگانه start شوند. همچنین این امکان وجود دارد، اگر دسترسی لازم را داشته باشیم، توسط اپلیکیشن‌های دیگری نیز start شوند.

Services

کامپوننتی است که در background نصب می­‌شود و user-interface ندارد. اغلب از سرویس‌ها برای کارهای طولانی مدت استفاده می‌شود. مانند پخش موزیک یا دانلود کردن یک فایل، بدون این‌که ui را مشغول کند. جذابیت این موضوع برای ما این است که سرویس‌ها می‌توانند یک remote interface را به وسیله‌ی AIDL و functionalityهایی نیز برای اپلیکیشن‌های دیگر داشته باشند. برخلاف سرویس‌های سیستمی که جزیی از سیستم‌عامل هستند و همیشه در وضعیت running قرار دارند، سرویس‌­های اپلکیشن‌ها می‌توانند در وضعیت Start یا Stop باشند.

Content Providers

در واقع یک interface است که برای اشتراک‌گذاری اطلاعات اپلیکیشن‌ها که به صورت عمومی در دیتابیس یا فایل‌ها ذخیره می­شوند، کاربرد دارد. یک نکته‌ی مهم این است که به وسیله‌ی IPC می‌توان به content provider دسترسی داشت واطلاعات اپلیکیشن را به وسیله‌ی اپلیکیشن‌های دیگر مشاهده کرد. بخش Content Providerها از کنترل دسترسی fine-grade استفاده می­کنند. در واقع از آن‌جایی که اجزای مختلف اطلاعات اپلیکیشن با هم متفاوت است، امکان به اشتراک گذاشته شدن زیر مجموعه­ای از اطلاعاتی که ذخیره شده، وجود دارد.

Broadcast Receivers

کامپوننتی است که به eventهایی که توسط سیستم‌عامل ایجاد یا Broadcast شده‌اند، پاسخ می‌دهد. به عنوان مثال اگر تغییرات سیستمی در اتصالات شبکه پیش آمده یا به روزرسانی و تغییرات اپلیکیشن در background به پایان رسیده باشد، event مربوط به آن توسط سیستم‌عامل Broadcast خواهد شد.

مدل امنیتی اندروید

مانند بقیه اجزای سیستم‌عامل، مدل امنیتی اندروید از مزیت‌هایی که کرنل در اختیار آن می­گذارد، بهره می‌برد. لینوکس یک سیستم‌عامل چند کاربره است که می­تواند منابع مربوط به یک کاربر را از دید کاربر دیگر مخفی یا برای فرآیندها محیط ایزوله­ای را فراهم کند. اندروید از این مزیت استفاده می‌کند اما باید به این نکته توجه داشت که مانند لینوکس سنتی (نسخه‌های Desktop یا Server)، عمل نمی­کند. در یک لینوکس سنتی، مقداری به نام UID به کاربر فیزیکی که می‌خواهد به سیستم‌عامل Login کند، یک سرویس سیستمی یا daemon که در background اجرا می­شود، اختصاص می­یابد. (به طور معمول هر daemon بر روی شبکه قابل دسترسی است و با محدود کردن یک daemon مشخص، می­توان از ضربه زدن به سیستم‌عامل جلوگیری کرد.)
اندروید به صورت پیش‌فرض برای smartphoneها ساخته شده است. به همین علت برای موبایل‌ها که وسیله‌هایی شخصی هستند، دیگر نیازی به ثبت user دیگری در سیستم نیست و سیستم‌عامل فقط با یک کاربر فیزیکی در تماس خواهد بود. در نتیجه از UID برای مشخص کردن اپلیکیشن‌های مختلف استفاده می‌شود. این موضوع، پایه‌ای برای sandboxing در اپلیکیشن‌های اندرویدی است.

Application Sandboxing

اندروید به صورت خودکار یک مقدار UID یکتا و خاص را به هر اپلیکیشنی می­دهد که به آن app id می­گویند. زمانی که اپلیکیشن نصب می­شود، UID به آن اختصاص می‌یابد و اگر اپلیکیشن اجرا شود، UID نیز در آن به اجرا در می­آید. همچنین به هر اپلیکیشن یک پوشه اختصاص داده خواهد شد که در آن پوشه فقط اجازه‌ی read و write داده می‌شود. در نتیجه اپلیکیشن‌ها در دو سطح مختلف ایزوله یا به اصطلاح sandbox می­شوند. سطح اول در لایه‌ی فرآیند، با هربار اجرا در یک process مشخص، و سطح دوم در لایه‌‎‌ی فایل است. همچنین هر اپلیکیشن یک دایرکتوری مجزا دارد. این قابلیت‌ها که یک sandbox در سطح kernel ایجاد می­کند، روی همه‌ی اپلیکیشن‌ها اعمال می­شود و دیگر فرقی در شیوه‌ی اجرا شدن آن‌ها که می­توانند به صورت native یا vm باشند، ندارد.
Daemonهای سیستم‌عامل و اپلیکیشن‌ها در UIDهای دسته‌بندی شده و ثابت که از یک‌دیگر به خوبی تفکیک شده­اند، اجرا خواهند شد. Daemonهای خیلی کمی نیز با دسترسی root اجرا می­شوند. سیستم‌عامل اندروید فایل /etc/shadows سنتی را ندارد ولی UIDهای سیستمی در header فایل android_filesystem_config.h به صورت ثابت مشخص شده­اند. UIDهایی که برای سرویس­های سیستم‌عامل استفاده می‌شوند، از شماره‌ی ۱۰۰۰ شروع می‌گردند، و خود این شماره نیز برای کاربر system است که privilegeهای مخصوص به خود را دارد، اما باز هم محدودیت هایی وجود دارد. به صورت خودکار شماره UID برای اپلیکیشن ها از ۱۰۰۰۰ شروع می­شود. مقداری به عنوان نام کاربری نیز در قالب app_XXX یا uY_aXXX ساخته می­شود که البته این قالب برای نسخه‌هایی از اندروید است که از چند کاربر پشتیبانی می­کنند. XXX نشان‌دهنده‌ی offset از aid_app و ِY نشان‌دهنده‌ی ID کاربر اندروید است. به عنوان مثال UID با عدد ۱۰۰۳۷ به صورت u0_a37 نیز قابل نوشتن است که به اپلیکیشنی به نام google email نسبت داده می­شود. همان‌طور که در تصویر زیر مشاهده می‌کنید package name آن نیز com.google.android.email است.
اطلاعات مربوط به اپلیکیشن ایمیل در این مثال بعد از package name در دایرکتوری /data/data/ قرار دارد. مالک یا owner تمام فایل‌هایی که در این پوشه قرار دارد، کاربر لینوکس است (u0_a37). اپلیکیشن‌ها این اختیار را دارند که فایل‌هایی را با فلگ MODE_WORLD_READABLE و یا MODE_WORLD_WRITEABLE تولید کنند. این کار با هدف دسترسی بقیه‌ی اپلیکیشن‌ها به اطلاعات است به‌گونه‌ای‌که اگر بخواهیم در لایه‌ی پایین­تر نیز به این موضوع نگاه کنیم، بیت­های S_IROTH و S_IWOTH برای به دست آوردن این دسترسی تنظیم می­شوند. البته اشتراک مستقیم فایل­ها در اندروید 4.2 به بالا منسوخ شده است. به عنوان مثال اطلاعات ذخیره شده‌ی اپلیکیشن ایمیل در یک دستگاه اندرویدی در شکل زیر نمایش داده شده است.
UID مربوط به هر اپلیکیشن و metadata آن package در فایل data/system/packages.xml و data/system/packages.list ذخیره شده است. خروجی دستور شکل زیر، UID که به com.google.android.email نسبت داده شده و در فایل packages.list است را نشان می­دهد.
در دستور بالا فیلد اول package name، فیلد دوم UID (که به اپلیکیشن داده شده)، فیلد سوم debuggable flag (اگر 1 باشد debuggable است)، فیلد چهارم آدرس پوشه‌ی مربوط به اطلاعات اپلیکیشن، فیلد پنجم seinfo (توسط SELinux استفاده می­شود) و در نهایت فیلد آخر هم یک فیلد متمم GID است که اپلیکیشن‌ها با آن اجرا می­شوند. ذکر این نکته مهم است که هر GID به یک permission در اندروید map می­شود و در این لیست، هر GID بر اساس سطح دسترسی‌ای که اپلیکیشن دارد، تولید خواهد شد.

Permissions

به دلیل اینکه اپلیکیشن‌های اندرویدی در sandbox هستند، تنها می­توانند به فایل‌های خودشان و یا فایل­هایی که از قبل برای آن‌ها تعریف شده، دسترسی داشته باشند. البته این محدودیت اپلیکیشن‌ها از دیدگاه توسعه دهندگان سیستم‌عامل اندروید، مناسب نیست. در اندروید علاوه بر این فایل­ها، می­توان دسترسی‌های جدیدی تعریف کرد که منجر به کارایی بهتر اپلیکیشن‌ها می­شود. به این حقوق دسترسی، permission گفته می­شود. کاربرد آن، کنترل دسترسی به Hardware Devices، Internet Connectivity، Data یا سرویس‌های سیستم‌عامل است. اپلیکیشن‌ها می­توانند دسترسی مورد نیاز خود را در فایل AndroidManifest.xml ثبت کنند. در مرحله‌ی نصب، دسترسی­های درخواست شده مورد بررسی کاربر قرار می­گیرد. تنها با یک‌بار اعطای دسترسی، نمی‌توان آن را revoke کرد و در نتیجه دیگر نیازی به تایید دوباره‌ی دسترسی وجود ندارد. البته در مواقع محدودی مانند دسترسی به private key یا user account نیاز به تایید دسترسی دوباره می­باشد. دسترسی­هایی وجود دارد که فقط به سه نوع اپلیکیشن داده می­شود. دسته‌ی اول اپلیکیشن­هایی که جزیی از سیستم‌عامل اندروید هستند. دسته‌ی دوم اپلیکیشن‌های preinstall هستند که به آن‌ها از قبل دسترسی داده می­شود و در نهایت دسته‌ی سوم نیز شامل اپلیکیشن‌هایی که توسط کلید سیستم‌عامل sign شده‌اند می‌شود و دسترسی‌های مربوطه به آن‌ها داده می­شود. اپلیکیشن‌های third-party می­توانند دسترسی­های دل‌خواه خود را داشته باشند و محدودیت­های مشابهی را تعریف کنند که به نام protection levels است. به این وسیله دسترسی به سرویس­های اپلیکیشن و منابعی که اپلیکیشن از آن‌ها استفاده می­کند، محدود می­شود.

Code Signing and Platform Keys

تمام اپلیکیشن‌ها (حتی اپلیکیشن‌های system) باید توسط توسعه دهنده‌ی آن امضا شوند. به این دلیل که فایل‌های با فرمت APK یک extension از قالب java jar package هستد، روی‌کرد امضا نیز بر اساس JAR است. کاربرد امضای هر APK در اندروید این است که سیستم‌عامل از بابت به روزرسانی که برای یک اپلیکیشن دریافت می­شود، اطمینان حاصل کند (که به آن same origin policy نیز گفته می­شود). این روی‌کرد به وسیله‌ی مقایسه‌ی امضای گواهینامه‌ی اپلیکیشنی که در دستگاه نصب شده، با امضای آن اپلیکیشنی که دانلود شده و نیازمند آپدیت است، فراهم می­شود.
اپلیکیشن‌های سیستمی نیز توسط کلیدهایی به نام platform key امضا می­شوند. کامپوننت­های مختلف سیستم‌عامل وقتی که با یک platform key امضا شده باشند، می­توانند منابعی که در اختیار دارند را با یکدیگر در یک process به اشتراک بگذارند. این platform keyها توسط همان کسی که از آن نسخه اندروید مراقبت و نگهداری می­کند، تولید و کنترل خواهد شد. به عنوان مثال، تولید کننده‌ها، carrierها، Google برای دستگاه های Nexus و یا حتی خود کاربران برای نسخه‌های اندروید self-build.

SELINUX

مدل امنیت سنتی اندروید اتکای زیادی به UIDها و GIDهایی که مربوط به اپلیکیشن هستند، دارد. با این که این مدل توسط کرنل تضمین شده است (به صورت پیش‌فرض همه‌ی فایل‌های اطلاعات مربوط به اپلیکیشن خصوصی هستند)، هیچ مکانیزمی وجود ندارد که از اعطای دسترسی به فایل‌های یک اپلیکیشن جلوگیری کند. مثل وقوع یک خطای برنامه‌نویسی یا هر مورد دیگری. هیچ چیزی نمی‌تواند باعث سواستفاده نکردن اپلیکیشن مخرب از permissionها و سوکت های محلی شود.
در واقع اعطای permission به بعضی از اپلیکیشن‌ها و یا system fileها، می­تواند منبع آسیب‌پذیری­های اندروید باشد. این آسیب‌پذیری‌ها بر طبق مدل کنترل دسترسی لینوکس که به نام Discretionary Access Control یا DAC است، قابل جلوگیری نیستند. DAC به این معنیست که زمانی که کاربر دسترسی‌هایی را به یک سری از منابع خاص دریافت می­کند، این دسترسی­ها می‌توانند به وسیله‌ی کاربران دیگر نیز مورد استفاده قرار بگیرند. به عنوان مثال، تنظیم یک فایل به صورتی که همه بتوانند آن را بخوانند (world-readable). در مقابل، مکانیزم MAC وجود دارد که باعث می­شود دسترسی به منابع در کل سیستم، از طریق یک سری authorization ruleها که به آن­ها policy نیز گفته می­شود، صورت پذیرد. policy فقط زمانی تغییر می‌یابد که administrator آن تغییرات را اعمال نماید. به دلیل این که کاربرها نتوانند یک فایل را override یا آن را برای همه‌ی کاربران دیگر قابل دسترس کنند.
بحث SELinux یا Security Enhanced Linux یک مکانیزم MAC است که برای کرنل لینوکس فراهم شده و بیشتر از ۱۰ سال است که مورد استفاده قرار می‌گیرد. در نسخه‌ی ۴.۳ اندروید، مکانیزمی شبیه به SELinux به نام SEAndroid تعبیه شده که به وسیله‌ی آن بتوان قابلیت‌هایی که در اندروید وجود دارد (مثل Binder)، در مکانیزم SELinux گنجانده شود. در اندروید، SELinux برای ایزوله کردن daemonهای اصلی اندروید و اپلیکیشن‌ها استفاده می‌شود، که به آن‌ها domainهای متفاوتی نسبت داده شده است. همچنین برای هر domain، یک policy متناظر تنظیم می‌شود. در نسخه‌ی ۴.۴ اندروید، SELinux در حالت enforcing mode پیاده‌سازی شده است که اجبار می­کند از policyهای سیستم برای تولید خطاهای زمان یا runtime استفاده شود. اما policy enforcement تنها روی daemonهای اصلی سیستم اعمال خواهد شد. به عبارت دیگر اپلیکیشن‌ها در حالت permissive mode اجرا می­شوند و log مربوط به آن نیز تولید می­شود. لازم به ذکر است این فرآنید باعث به وجود آمدن خطای runtime نمی­شود.

جمع‌بندی

در این دو مقاله، قصد داشتیم که خواننده با مباحث پایه‌ی امنیت در اندروید یا همان مبانی معماری امنیتی اندروید، آشنا شود. اصطلاحاتی مانند Activity، Receiver و Permission که در دنیای امروز امنیت اپلیکیشن‌های اندرویدی نقش مهمی دارند را نیز به زبان ساده شرح دادیم. البته مکانیزم‌هایی مانند IPC در سیستم‌عامل اندروید نیز بیان شد که در فرآیند نفوذ به اپلیکیشن‌های اندرویدی نقشی ندارند و بیشتر در لایه‌های زیرین سیستم‌عامل اندروید مطرح هستند.
با استفاده از مطالبی که در این دو مقاله شرح داده شد، می‌توانید اپلیکیشن‌های اندرویدی را از نظر امنیتی بررسی کنید. با توجه به قابلیت فوق‌العاده شخصی‌سازی در اندروید (که از کرنل لینوکسی آن نشات گرفته شده)، توسعه‌دهندگان می‌توانند از مکانیزم‌های کنترلی و امنیتی متفاوتی به منظور جلوگیری از نفوذ هکرها به آن استفاده نمایند.

ویدیوی شرح مقاله توسط «محمدرضا تیموری»