برای مطالعهی بخش اول این مقاله به
اینجا مراجعه کنید.
بخش ششم – کتابخانههای فریمورک اندروید (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 در سیستمعامل اندروید نیز بیان شد که در فرآیند نفوذ به اپلیکیشنهای اندرویدی نقشی ندارند و بیشتر در لایههای زیرین سیستمعامل اندروید مطرح هستند.
با استفاده از مطالبی که در این دو مقاله شرح داده شد، میتوانید اپلیکیشنهای اندرویدی را از نظر امنیتی بررسی کنید. با توجه به قابلیت فوقالعاده شخصیسازی در اندروید (که از کرنل لینوکسی آن نشات گرفته شده)، توسعهدهندگان میتوانند از مکانیزمهای کنترلی و امنیتی متفاوتی به منظور جلوگیری از نفوذ هکرها به آن استفاده نمایند.
ویدیوی شرح مقاله توسط «محمدرضا تیموری»