عماد عابدینی
عماد عابدینی
خواندن ۱۱ دقیقه·۱ سال پیش

روش Intercept ترافیک در اپلیکیشن‌های Flutter ی

یکی از اصلی‌ترین بخش‌ها در کرک، مهندسی معکوس و تست نفوذ اپلیکیشن‌های اندرویدی Intercept کردن ترافیکشون میشه.

مراحل کلی به این صورت میشه:

  • نصب اپلیکیشن سر Device فیزیکی و یا Emulator
  • سِت کردن Proxy سر دیوایس/شبیه ساز
  • اضافه کردن CA مربوط به Proxy Server به عنوان CA مورد تایید
  • گرفتن ترافیک با ابزار‌هایی مثل Burp Suite و یا Zap

حالا این وسط خیلی وقتا نیازه الگوریتم‌های حفاظتی اپلیکیشن مثل SSL Pinning، Root Detection و Emulator Detection رو هم Bypass کنیم.

اگه با روش‌های بالا بردن امنیت اپلیکیشن‌های اندرویدی آشنا نیستین، این پست ویرگول میتونه مفید باشه.
AI Image
AI Image


برای طولانی نشدن این پست، فرض شده که شما با مقدمات مهندسی معکوس اپلیکیشن‌های اندرویدی آشنا هستین و بنابراین خیلی از مباحث گفته نشده.

خب برگردیم سر موضوع خودمون...
یه مشکلی (چالشی) که برای Intercept ترافیک وجود داره اینه که این روزا میشه با زبان‌ و فریمورک‌های زیادی اپلیکیشن اندرویدی نوشت، مثل:

Java, Kotlin, Flutter, React Native, Xamarin, ...

که هر کدوم از روش خاص خودشون برای حفاظت از Intercept ترافیکشون استفاده می‌کنن و طبیعتا روش Bypass شون هم نسبت به هم فرق داره (تا حدودی)

اگه با مباحث مقدماتی Intercept ترافیک اپلیکیشن‌های اندرویدی آشنا نیستین، لینک زیر میتونه براتون مفید باشه:
https://redfoxsec.com/blog/ssl-pinning-bypass-android-frida/

حالا از بین این زبان و فریمورک‌ها، اپلیکیشن‌هایی که با Java و Kotlin توسعه داده شدن معمولا Bypass کردنشون به سادگی انجام میشه، اصلی‌ترین دلیلش اینه که کلی کتابخونه و روش آماده برای حفاظت از ترافیکشون دارن که با یه سرچ ساده میشه بهشون رسید، حالا مشکل کجاست؟ مشکل اینجاست که برای اکثر این کتابخونه و روش‌های آماده، اسکریپت Bypass شون هم به صورت Public موجوده 🙂
و جالبه بدونین اکثر اپلیکیشن‌های اندرویدی که با این دو زبان نوشته شدن، اومدن دقیقا از همین روش و کتابخونه‌های آماده‌ استفاده کردن! (حتی خیلی از اپلیکیشن‌های بانکی و پرداخت! 😶)

میشه از همین کتابخونه و روش‌های آماده هم استفاده کرد ولی باید تا جایی که میتونیم تغییرشون بدیم که حداقل با اسکریپت‌های Bypassی که Public موجوده، نشه به سادگی Bypassشون رو انجام داد.

شاید اینجا سوال پیش بیاد که اگه اپلیکیشنمون رو با فلاتر توسعه بدیم، نسبت به مثلا Java و Koltin امن تره؟

  • اگه دانش و اطلاعاتمون از امنیت خیلی خوب نیست، بله، اپلیکیشنی که با فلاتر توسعه داده میشه به صورت پیشفرض امنیت بیشتری داره چون فلاتر خیلی‌ کارارو به صورت پیشفرض خودش برای بالا بردن امنیت و سخت‌تر شدن مهندسی معکوس انجام میده.
  • ولی اگه دانش امنیتی خوبی داریم و قراره برای بالا بردن امنیت اپلیکیشنی که می‌خوایم توسعه بدیم از روش‌های اختصاصی استفاده کنیم، میشه با Java و Koltin هم اپلیکیشن بسیار امنی (خیلی امن تر از فلاتر) نوشت.

خب رسیدیم به اینجا که گفتیم Intercept ترافیک برای اپلیکیشنی که با Java و Kotlin نوشته شده، نسبت به بقیه زبان و فریمورک‌ها خیلی راحت‌تر و سریع تره. (اگه از روش‌های عمومی استفاده کرده باشه، معمولا کمتر از 1 دقیقه انجام میشه)

AI Image
AI Image


ولی...

داستان برای اپلیکیشن‌هایی که با Flutter توسعه داده شدن یکم فرق می‌کنه. از چه نظر فرق می‌کنه؟
اپلیکیشنی که با فلاتر توسعه داده شده:

  • از کانفیگ Proxy تعریف شده سیستم عامل پیروی نمی‌کنه.
  • به CAهای تعریف شده سیستم عامل کاری نداره و از Certificate Store اختصاصی خودش استفاده می‌کنه.

مورد اول که مشخصه، اپلیکیشن‌های فلاتری کاری ندارن ما سر سیستم عامل Proxy تعریف کردیم یا نه.
مورد دوم هم اگه بخوایم توضیح بدیم، ما زمانی می‌تونیم ترافیک HTTPS رو با Proxy بگیریم که CA پراکسی سرور به CAهای مورد اعتماد سیستم‌عامل اضافه شده باشه، اینجوری زمانی که بخواد Valid بودن Certificate رو چک کنه، تاییدش رو از CA ای که اضافه کردیم می‌گیره. ولی اپلیکیشنی که با فلاتر توسعه داده شده اصلا کاری به لیست CAهای سیستم عامل نداره و چک کردن رو با CAهای خودش انجام میده. 🤠

خب حالا چکار کنیم؟

اول از همه باید یه فکری برای حل مشکل Proxy کنیم چون همونطور که بالاتر صحبتش شد برای گرفتن ترافیک نیاز به Proxy کردن داریم، ولی مشکل اینجاست اپلیکیشن‌های فلاتری کاری ندارن ما سر سیستم عامل Proxy تعریف کردیم یا نه...

اولین چیزی که لازم داریم، یک دیوایس و یا شبیه‌ساز Root شدست. از اونجایی که سر نسخه‌های جدید اندروید دسترسی به root سخت شده، پس حالت راحتش اینه که بیایم از Emulator استفاده کنیم، مثلا از Nox Player

فعال کردن دسترسی به Root در Nox Player
فعال کردن دسترسی به Root در Nox Player


خب حالا که به Root دسترسی داریم، برای حل مشکل Proxy از دو روش می‌تونیم استفاده کنیم:

  • استفاده از اپلیکیشن ProxyDroid
  • استفاده از iptables

روش اول اینه که بیایم ProxyDroid رو نصب و اجرا کنیم. بعدش میایم IP و Port پراکسی سرور رو در بخش Proxy Setting وارد می‌کنیم. بعد از بخش individual proxy، اپلیکیشنی که می‌خوایم ترافیکش از Proxy عبور کنه رو انتخاب می‌کنیم. در مرحله آخر Proxy Switch رو فعال می‌کنیم:

تو این مرحله نیاز به Transparent Proxying داریم، تو Burp Suite اسمش رو گذاشته Invisible Proxying که باید به این صورت فعالش کنیم:

Invisible Proxying
Invisible Proxying


الان مشکل Proxy حل شده و در صورتی که الگوریتمی مثل Root Detection و SSL Pinning پیاده‌سازی نشده باشه، می‌تونیم ترافیک HTTP رو بگیریم. (Bypassشون و HTTPS رو در ادامه توضیح میدم)

بریم روش دوم...

روش دوم میشه استفاده از iptables ، به این صورت:

اینجا اومدیم یه rule تعریف کردیم که ترافیک به IP و Portی که تعریف کردیم reroute شه.
بعد از تعریف این rule باید Invisible Proxying رو هم فعال کنیم که روش فعال‌سازیش رو بالا توضیح دادم.

حالا می‌تونیم ترافیک HTTP رو بگیریم (البته بازم با فرض اینکه الگوریتمی مثل Root Detection و SSL Pinning پیاده‌سازی نشده باشه)

خب پس تا اینجای کار مشکل Proxy نشدن رو به صورت کامل حل کردیم.


حالا HTTPS و SSL Pinning رو چکار کنیم؟

همونطور که بالاتر توضیح دادم فلاتر به CAهای سیستم عامل کاری نداره و Certificate رو با CA های خودش چک میکنه، پس حتی با فرض اینکه اپلیکیشن از هیچ الگوریتم امنیتی‌ای استفاده نکرده باشه، بازم نمی‌تونیم HTTPS رو بگیریم 🙂 چکار کنیم؟

باید بیایم اپلیکیشن رو Decompile کنیم و اون بخشی از کد که مربوط به SSL Pinning میشه رو دورش بزنیم. برای این کار از دو روش می‌تونیم استفاده کنیم، یکی اینکه بیایم بخش مربوط به SSL Pinning رو پیدا کنیم و حذفش کنیم (یا شرطش رو برعکس کنیم که چک نشه) و مجدد Rebuild بگیریم و اپلیکیشن رو نصب کنیم (تو این حالت میتونیم ترافیک HTTPS رو هم بگیریم)

اگه با روش کرک اپلیکیشن‌های اندرویدی آشنا نیستین، این دوره ویدیویی می‌تونه براتون مفید باشه.

یک راه دیگه‌ش هم اینه که بیایم function مربوط به SSL Pinning رو hookش کنیم. (من این روش رو بیشتر توصیه می‌کنم، اینجوری دیگه نیازی به Recompile نیست و تغییری که می‌خوایم رو می‌تونیم runtime انجام بدیم)

اینکه hook شدن رو توضیح نمیدم چون همونطور که اول پست گفته شد فرض شده شما با مقدمات مهندسی معکوس اپلیکیشن‌های اندرویدی آشنا هستین.

خب توی هر دو راه، اول باید اون قسمتی از کد که مربوط به SSL Pinning میشه رو پیدا کنیم، برای این کار میایم APKمون رو از حالت archive خارج می‌کنیم و فایل libflutter.so رو از مسیر زیر برمی‌داریم:

\lib\ [CPU architecture] \libslutter.so

حالا باید بازش کنیم، برای باز کردنش میشه از ابزارهای زیادی مثل OllyDbg ، JEB ، IDA Pro و Ghidra استفاده کرد که البته برای کاری که ما می‌خوایم انجام بدیم Ghidra انتخاب مناسب تریه.

پس اول با Ghidra بازش می‌کنیم:
(موقعی که بازش می‌کنیم میگه فایل رو Analyze کنه یا نه، شما بزنین Analyze کنه)

اینجا با Ghidra بازش کردیم
اینجا با Ghidra بازش کردیم


حالا مقدار String زیر رو از بخش Search و بعد For Strings فیلتر می‌کنیم: (باید صبر کنین Analyze کارش تموم شده باشه. نسبت به سیستمتون ممکنه زمان بگیره)

ssl_x509
فیلتر کردن ssl_x509 از Stringها
فیلتر کردن ssl_x509 از Stringها


حالا اگه دو بار روش کلیک کنیم، فانکشن‌هایی که این مقدار (ssl_x509) داخلشون هست رو می‌تونیم ببینیم:

همونطور که مشخصه مقداری که دنبالش هستیم یه جایی تو محدوده 0x300000 هست ولی ما نیاز داریم دقیقش رو پیدا کنیم. اینجا 5 تا فانکشن داریم که این مقدار داخلشون استفاده شده و روی هر کدوم که کلیک کنیم می‌تونیم محتواش رو ببینیم. ولی ما باید فانکشن اصلی‌ای که وظیفه SSL Pinning رو بر عهده داره رو پیدا کنیم، چجوری؟ خیلی راحت
روش اصلی اینه که بیایم هر فانکشن رو جداگونه بررسی کنیم ببینیم کدوم فانکشن اصلیه که زمان زیادی می‌گیره. برای این که کار راحت‌تر و سریع‌تر شه می‌تونیم از فانکشن‌های نمونه زیر استفاده کنیم:

فانکشن مربوط به SSL Pinning در معماری 32 بیت:

32 بیت
32 بیت


فانکشن مربوط به SSL Pinning در معماری 64 بیت:

64 بیت
64 بیت


در واقع تنها کاری که لازمه انجام بدیم اینه که از فانکشن‌هایی که پیدا شده (همون 5 تا) دنبال فانکشنی باشیم که شبیه به این دو نمونه بالاست. ( با یه نگاه کلی می‌تونین پیداش کنین).
الان مثلا تو این مثال ما فانکشن 0x3e236c میشه همون فانکشنی که دنبالش هستیم:

به معماری Device/Emulator که اپلیکیشن رو سرش نصب کردین حتما دقت کنید.
FUNCTION 0x3e263c
FUNCTION 0x3e263c


اگه به فانکشن 0x3e236c دقت کنید تا حد خیلی زیادی شبیه به همون نمونه فانکشن بالا میشه. (البته همیشه اینطوری نیست که تا این حد شبیه باشه، ولی حداقل 80% شبیه‌ هستن و با یه نگاه 5 ثانیه‌ای پیداش می‌کنین)

پس تا اینجای کار ما فانکشنی که SSL Pinning رو انجام میده رو پیدا کردیم، ولی اینجا یه نکته وجود داره، ما برای باز کردن libflutter.so از Ghidra استفاده کردیم، Ghidra آدرس‌دهی رو از 0x100000 شروع می‌کنه پس باید 0x100000 رو از 0x3e236c کم کنیم، که میشه 0x2e236c

البته میشه به جای اینکه این مراحل رو به صورت دستی انجام بدیم، از این فانکشن یه signature در بیاریم و با کمک Frida خیلی سریع این فانکشن رو پیدا کنیم، ولی این روش رو خیلی پیشنهاد نمی‌کنم چون اینجور که تست کردم signatureی که در میاریم فقط سر همون version فلاتر جواب میده. یعنی شما با signature مربوط به فلاتر به فرض 2.0.0 نمی‌تونین این فانکشن رو سر 3.0.0 پیدا کنین، ولی روش دستی همیشه جواب میده.

خب حالا که فانکشن و آدرسش رو پیدا کردیم میریم که hookش کنیم، به این صورت:

تو اسکریپت بالا به جای ADDRESS ، چیزی که پیدا کردیم رو میذاریم (تو مثال بالا میشه 0x2e236c)

خب تموم شد... 😍
الان میتونیم ترافیک رو بگیریم:


ولی، اگه از Root Detection استفاده کرد بود چی!؟

اگه Root Detection پیاده‌سازی شده باشه معمولا همون اول کار زمانی که اپلیکیشن باز میشه (معمولا تو Splash) روت بودن چک میشه و در صورتی که Root بودن تشخیص داده بشه، اپلیکیشن بسته میشه و اصلا کار به گرفتن ترافیک نمیرسه (Splash به صفحه بعدی نمیره)

چجوری Bypassش کنیم؟
روش کار تا حدودی مشابه به روش Bypass کردن SSL Pinning می‌مونه که بالاتر توضیح دادم. یعنی اول از همه باید اپلیکیشن رو دیکامپایل و بعد بخشی از کد که Root بودن رو چک میکنه رو پیدا کنیم. حالا دوباره مثل روش قبل، یا میتونیم hookش کنیم و یا حذفش کنیم و مجدد Rebuid بگیریم.

حالا چجوری خیلی سریع بخشی از کد که وظیفه بررسی Root بودن داره رو پیدا کنیم؟
من از این دو روش استفاده می‌کنم و اکثر وقتا جواب میده: (اگه با این دو روش پیدا نشد باید یکم وقت بذاریم و با گشتن تو سورس کد پیداش کنیم)

  • جستجو مقدار استرینگ "su" در سورس کد استخراجی
  • دیکامپایل با Apktool و جستجو مقدار استرینگ "root" در بخش Packageها (از این نظر که معمولا پکیج‌های عمومی که برای این منظور هستن تو اسمشون root داره)

مثلا فرض کنید چک کردیم دیدم اپلیکیشن از rootbeer برای بررسی root بودن استفاده کرده، حالا یا میتونیم بیایم اون شرطی که از نتیجه rootbeer استفاده کرده رو برعکسش کنیم و مجدد Rebuild بگیریم و یا اینکه هوکش کنیم.
مثلا اینجا اومدیم hookش کردیم که نتیجه اینکه Device/Emulator روت شده هست یا نه رو تحت هر شرایط false برگردونه 🙂

اینجوری Root Detection هم Bypass میشه... 😍


موضوع خاص نگفته‌ای به نظرم نمیرسه. هدف از این پست این بود که ببینیم گرفتن ترافیک اپلیکیشن‌هایی که با فلاتر نوشته شدن بر خلاف چیزی که شاید خیلی‌ها فکر کنن اصلا سخت نیست و فقط روش کار یکم فرق میکنه. در واقع از هر زبان و فریمورکی که استفاده کنیم، اگه بخوایم از روش‌های پیشفرض و یا عمومی برای بالا بردن امنیت استفاده کنیم، روش Bypassش اصلا سخت نیست.

اگه از روش‌های اختصاصی هم استفاده کنیم، در واقع فقط اومدیم Bypass کردنش رو سخت‌تر کردیم، وگرنه بازم در هر صورت نمی‌تونیم جلوش رو بگیریم.
تا زمان انتشار این پست، روشی که برای گرفتن ترافیک اپلیکیشن‌های فلاتری استفاده میشه همین روشی میشه که تو این پست توضیح داده شد. ولی تا الان که نسخه 3.7.2 فلاتر منتشر شده، 4 بار فلاتر بخش مربوط به حفاظت از ترافیکش رو تغییرات جزئی داد و خب روش Bypassش هم تا حدودی تغییر کرد ولی روش کلی همین روش بالا بود و فقط نیاز به یکم تغییرات کوچیک داشت.

راستی اگه به تحلیل اپلیکیشن علاقه دارین احتمالا این پست ویرگول و ویدیو زیر هم براتون جالب باشه:

https://www.aparat.com/v/LW94a


امیدوارم این پست براتون مفید بوده باشه.
شاد و موفق باشین ❤️

فلاترتست نفوذبرنامه نویسیflutterامنیت
Security Researcher | Full Stack Developer
شاید از این پست‌ها خوشتان بیاید