<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های عماد عابدینی</title>
        <link>https://virgool.io/feed/@emad_abedini</link>
        <description>Security Researcher | Full Stack Developer</description>
        <language>fa</language>
        <pubDate>2026-04-15 10:15:43</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/20360/avatar/aTZVJu.png?height=120&amp;width=120</url>
            <title>عماد عابدینی</title>
            <link>https://virgool.io/@emad_abedini</link>
        </image>

                    <item>
                <title>روش تحلیل بدافزارهای Obfuscate‌ شده‌ی Golang</title>
                <link>https://virgool.io/@emad_abedini/%D8%B1%D9%88%D8%B4-%D8%AA%D8%AD%D9%84%DB%8C%D9%84-%D8%A8%D8%AF%D8%A7%D9%81%D8%B2%D8%A7%D8%B1%D9%87%D8%A7%DB%8C-obfuscate-%D8%B4%D8%AF%D9%87-%DB%8C-golang-xoaoexzs5qhc</link>
                <description>در فرایند تحلیل بدافزارها، نمونه‌هایی که با Go (Golang) نوشته می‌شن معمولا نسبت به زبان‌هایی مثل C تحلیل پیچیده‌تری دارن، به‌ خصوص زمانی که بدافزار Obfuscate هم شده باشه. در این ویدیوی 42 دقیقه‌ای، روش تحلیل این دسته از بدافزارها رو توضیح دادم.بخشی از موضوعاتی که در این ویدیو بررسی می‌کنیم:چرا Golang برای توسعه‌ی بدافزار گزینه‌ی مناسبیه؟چه عواملی باعث می‌شن تحلیل بدافزارهای Go نسبت به C پیچیده‌تر باشه؟معرفی تعدادی از ابزارهای کاربردی برای تحلیل فایل‌های Goبخش عملی (از دقیقه ۷ به بعد):یک اسکریپت Go رو با روش‌های مختلف Build می‌گیریم و بررسی می‌کنیم هر روش چه تأثیری روی تحلیل خروجی می‌ذاره.در مرحله‌ی بعد، با استفاده از ابزار Garble که یکی از قوی‌ترین ابزارهای Obfuscation در Go محسوب می‌شه، فایل رو Obfuscate می‌کنیم و بعد از بررسی خروجی، سراغ نحوه‌ی تحلیلش می‌ریم.در نهایت به این نتیجه می‌رسیم که تحلیل بدافزارهای Go، برخلاف تصور، به اون پیچیدگی‌ای که تصور می‌شه نیست و با رویکرد درست می‌شه به‌راحتی انجامش داد.لینک ویدیو در آپارات:https://www.aparat.com/v/mcnr064امیدوارم مفید باشه ❤️</description>
                <category>عماد عابدینی</category>
                <author>عماد عابدینی</author>
                <pubDate>Mon, 22 Dec 2025 19:49:35 +0330</pubDate>
            </item>
                    <item>
                <title>روش Decrypt ترافیک AES در اپلیکیشن‌های Flutter</title>
                <link>https://virgool.io/@emad_abedini/%D8%B1%D9%88%D8%B4-decrypt-%D8%AA%D8%B1%D8%A7%D9%81%DB%8C%DA%A9-aes-%D8%AF%D8%B1-%D8%A7%D9%BE%D9%84%DB%8C%DA%A9%DB%8C%D8%B4%D9%86-%D9%87%D8%A7%DB%8C-flutter-xnhzxelrblql</link>
                <description>در فرایند تست نفوذ اپلیکیشن‌های اندرویدی، گرفتن ترافیک اینترنت یکی از مراحل اصلی محسوب میشه. بنابراین از دید یک توسعه‌دهنده تلاش می‌کنیم با پیاده‌سازی مکانیزم‌های مختلف این فرایند رو پیچیده‌تر کنیم.در این پست فرض شده که شما با مفاهیم و فرایند کلی تست نفوذ اپلیکیشن‌های اندرویدی آشنا هستید. اگه آشنایی ندارید، پیشنهاد می‌کنم اول پست‌های قبلی رو مطالعه کنین.اوایل تنها مکانیزم امنیتی، Obfuscation و استفاده از SSL بود، ولی به مرور مکانیزم‌های دیگه‌ای به اپلیکیشن‌ها اضافه شد، مثل:تشخیص اجرا روی شبیه ساز (Emulator Detection)تشخیص روت بودن دستگاه (Root Detection)تشخیص استفاده از VPN/Proxyپیاده‌سازی SSL Pinningنادیده گرفتن System Proxyرمزنگاری ترافیک ارسالی/دریافتیپیاده‌سازی لایه‌های امنیتی به صورت native (با C و ++C)هر کدوم از مکانیزم‌های بالا مثل هر راهکار امنیتی دیگه، تا زمانی که روش Bypass‌ش عمومی نشده باشه عملکرد خوبی می‌تونه داشته باشه، اما معمولا خیلی زود روش‌ها و ابزارهایی برای دور زدن هر الگوریتم امنیتی به صورت عمومی منتشر میشه. همین الان برای تموم مواردی که بالا اسم برده شد کلی ابزار و اسکریپت عمومی وجود داره که فقط با چند کلیک میشه Bypassشون رو انجام داد.اگه قراره به راحتی Bypass بشن پس پیاده‌سازی‌شون چه فایده‌ای داره!؟ این موضوع تا حد زیادی به نحوه پیاده‌سازی بستگی داره. اگه پست‌های قبلی رو دنبال کرده باشین همیشه تاکید کردم لایه‌های امنیتی رو تا جای ممکن با روش و الگوریتم اختصاصی خودتون پیاده‌سازی کنین، یا اگه خواستین از اینترنت کپی کنین حتما تا حد امکان تغییرش بدین. الگوریتم امنیتی‌ای که از اینترنت کپی شده باشه، مطمئن باشین دیر یا زود روش Bypass‌ش هم عمومی منتشر می‌شه. در واقع اکثر ابزارها و روش‌های Bypass بر اساس کتابخونه‌ها و الگوریتم‌های رایج توسعه داده شدن، خیلی وقت‌ها یک تغییر کوچیک تو کدی که از اینترنت کپی کردیم باعث می‌شه این ابزارها دیگه نتونن کار کنن.خلاصه که اگه این مکانیزم‌ها به‌صورت صحیح و شخصی‌ سازی‌ شده پیاده‌سازی بشن، می‌تونن تا حد زیادی فرایند Reverse رو زمان‌بر و پیچیده کنن.البته اینو هم در نظر داشته باشین که هدف کلی ما اینه که مکانیزم‌هایی رو اضافه کنیم تا باعث شن فرایند تحلیل و Reverse پیچیده‌تر بشه، وگرنه هر چقدرم مکانیزم‎‌ها رو اصولی پیاده‌سازی کنیم اونی که دانش کافی رو داره در نهایت راه دور زدنشو پیدا می‌کنه. پس حتی اگه حال و حوصله شخصی‌سازی رو ندارین، کپی کردن این الگوریتم‌ها از اینترنت بازم اونقدرا بی تاثیر نیست.اگه با مکانیزم‌های بالا و نحوه Bypassشون آشنایی ندارید، در پست‌های قبلی در موردشون توضیح دادم.توی این پست می‌خوایم سراغ اپلیکیشن‌هایی بریم که با Flutter توسعه داده شدن و ترافیک ارسالی/دریافتی‌شون رو رمزنگاری (Encrypt) می‌کنن.چرا Flutter ؟همونطور که می‌دونید در حال حاضر زبان‌ها و فریمورک‌های زیادی برای توسعه اپلیکیشن‌های اندرویدی وجود داره. یکی از روش‌هایی که باعث میشه فرایند Reverse زمان‌بر بشه نیاز به تحلیل فایل‌های باینریه. به‌ همین دلیل خیلی از اپلیکیشن‌هایی که امنیت براشون اهمیت داره (مثل اکثر اپ‌های مالی و پرداخت)، بخش‌های حساس رو به‌ صورت native پیاده‌سازی می‌کنن تا Reverse و Bypass لایه‌های امنیتی‌شون نیاز به تحلیل باینری داشته باشه. حالا از بین این زبان و فریمورک‎‌ها، وقتی شما با مثلا Rust، Go یا Flutter اپ اندرویدی توسعه می‌دید، خروجی به کد native (فایل so.) تبدیل می‌شه.درسته که می‌شه با استفاده از NDK تو اکثر زبان و فریمورک‌ها بخشی از پروژه رو native زد، اما موضوع اینجاست که بعضی از این زبان‌ و فریمورک‌ها، بدون اینکه نیاز باشه ++C/C بزنین، خودشون در زمان کامپایل خروجی رو به native code تبدیل می‌کنن.پس ما این پست رو به Flutter اختصاص دادیم چون:Flutter به‌صورت پیشفرض چندین مکانیزم امنیتی رو به خروجی اضافه می‌کنه.از روز اول انتشارش، دور زدن لایه‌های امنیتی اپ‌هایی که با Flutter ایجاد شده بودن چالش‌های زیادی داشت (بخش زیادی از این چالش‌ها سر این بود که تا قبل از فلاتر، اکثر اپ‌ها با Java/Kotlin پیاده‌سازی می‌شدن ولی از اونجایی که فلاتر خروجی رو به باینری تبدیل می‌کرد، خیلی از روش‌های Bypass قبلی دیگه جوابگو نبودن)در Flutter (در واقع Dart) خیلی از Objectها، Constها و Variableها به جای اینکه مستقیم در کد ذخیره شن، در یک جدول (Object Pool) قرار می‌گیرن و از طریق Index فراخوانی می‌شن. (بنابراین استخراج Stringها با ابزارهای رایجی که برای این منظور استفاده میشه کمکی نمی‌کنه)این روزا Flutter خیلی محبوبه، تا جایی که حتی خیلی از اپ‌های مالی و پرداخت هم با فلاتر توسعه داده شدن.روش Bypass لایه‌های امنیتی اپ‌های فلاتری رو در پست‌های قبلی توضیح دادم، تو این پست فقط روی روش Decrypt ترافیک تمرکز می‌کنیم.پیش نیاز‌ها:با مقدمات فرایند تست‌ نفوذ و Reverse اپلیکیشن‌های اندرویدی مثل کار با Frida و مفاهیم پایه معماری ARM آشنا باشید. (اگه آشنا نیستید نگران نباشید، با یه سرچ ساده یا استفاده از ChatBotها می‌تونید خیلی سریع یادشون بگیرین)یک دیوایس فیزیکی یا Emulator با معماری ARM64 داشته باشید. در کل برای انجام تست‌ نفوذ و Reverse اپ‌های اندرویدی بهتره از ARM64 استفاده کنید، چون این روزا اکثر Device ها این معماری رو دارن و بیشتر ابزارها هم با این معماری سازگاری و عملکرد بهتری دارن.و مورد آخر اینکه، مطالعه پست‌های مرتبط قبلی خیلی کمک‌ کننده‌ست. مطالبی که قبلا توضیح داده شدن رو برای اینکه پست طولانی‌تر نشه، اینجا دیگه تکرار نکردم.رمز کردن ترافیک در اپلیکیشن‌های اندرویدیرمزنگاری ترافیک چیز جدیدی نیست، سال‌هاست در اپلیکیشن‌ها داره استفاده می‌شه و پیاده‌سازی پیچیده‌ای هم نداره. در هر زبان و فریمورکی با اضافه‌ کردن فقط چند خط کد میشه ترافیک رو Encrypt کرد. در حال حاضر اکثر اپ‌های بانکی، بسیاری از اپ‌های دولتی و به‌طور کلی تعداد زیادی از اپ‌ها ترافیک اینترنت‌شون رو Encrypt شده ارسال و دریافت می‌کنن. حالا این وسط بعضی‌هاشون این لایه امنیتی رو اصولی‌ پیاده‌سازی کردن و این باعث شده فرایند Bypass نسبتا وقت‌گیری داشته باشن، یکسری دیگه هم اومدن از کتابخونه‌ و الگوریتم‌های عمومی استفاده کردن که اکثر وقت‌ها میشه فقط با چند کلیک راحت Bypassشون کرد.بدون در نظر گرفتن اپ‌های ایجاد شده با Flutter، به‌طور کلی وقتی به ترافیک Encrypt شده می‌رسیم معمولا به این صورت میریم جلو:بررسی می‌کنیم از چه زبان و فریمورکی استفاده شده و بعد دنبال نشونه‌هایی از کتابخونه‌ و الگوریتم‌های رایج مرتبط با رمزنگاری می‌گردیم (بر اساس نام)اگه نشونه‌ای از کتابخونه و الگوریتم‌های رایج پیدا کردیم، میریم سراغ Bypass با ابزار و Scriptهای عمومی مرتبط با اون کتابخونه یا الگوریتم (به احتمال خیلی زیاد هم به نتیجه می‌رسیم)اگه اسکریپت‌های Bypass عمومی جواب ندادن، کد رو دقیق‌تر بررسی می‌کنیم تا ببینیم الگوریتم‌های عمومی رو تغییر داده یا الگوریتم اختصاصی خودش رو پیاده‌سازی کرده و بعد بر اساس کدی که پیاده‌سازی شده بود، اسکریپت مورد نیاز برای Hookش رو آماده می‌کنیم. (روش کار رو تو پست‌های قبلی توضیح دادم)روش دیگه اینه که بیایم تمام فانکشن‌ها و کتابخونه‌های رایج مرتبط با رمزنگاری رو Hook کنیم (اسکریپت آماده‌ش هم هست)یا می‌تونیم اگه توابع مرتبط با رمزنگاری رو پیدا کردیم، بخشی ازش رو گوگل و مخصوصا Github سرچ کنیم. (این روش بارها برای من نتیجه داده، حتی پارامترها رو هم تغییر نداده بودن و کلید هم همون چیزی بود که تو Github بود، باور کردنی نیست نه؟ ولی خیلی پیش میاد)یک روش دیگه اینه که بعد از اجرای اپلیکیشن و ارسال اولین ترافیک، از فایل‌هایی که ایجاد کرده دنبال نشونه‌های از روش رمزنگاری و پارامترهای مرتبط با اون الگوریتم بگردیم. (بارها برام پیش اومده که با همین روش به نتیجه رسیدم، اومده بودن کلید و پارامترهای رمزنگاری رو گذاشته بودن تو Shared Preferences یا یک فایل دیگه اونم با اسم کاملا مشخص) 😶یا می‌تونیم در صورتی که Server دیتا رو به صورت Plain قبول می‌کرد، اون قسمت از کد که مقادیر رو رمزنگاری می‌کنه حذف کنیم و مجدد Rebuild بگیریم. (البته خیلی وقت‌ها این روش ممکنه وقت خیلی بیشتری نسبت به سایر روش‌ها بگیره)یا می‌تونیم بعد از اجرای اپلیکیشن و ارسال اولین ترافیک، Memory Mapping بگیریم، این روش هم خیلی وقت‌ها دیتای خوبی بهمون میده.روند کلی برای اپلیکیشن‌هایی که با فلاتر توسعه داده شدن هم تقریبا به همین صورته، ولی همونطور که بالاتر توضیح دادم در خروجی باینری‌ای که بعد از Decompile اپ‌های فلاتری داریم، اکثر Objectها به صورت مستقیم در کد وجود ندارن، بدون وجود Object و Stringها پیدا کردن بخشی که دنبالش هستیم می‌تونه بسیار وقت‌گیر و پیچیده باشه. تو این پست می‌خوایم بریم سراغ استفاده از ابزاری که تا حد زیادی این فرایند رو برامون سریع‌تر می‌کنه.روشی که می‌خوایم بریم جلو، تا نسخه فعلی فلاتر (3.35.0) داره جواب میده، ولی از اونجایی که فلاتر مرتب الگوریتم‌های امنیتی‌ش رو آپدیت می‌کنه، ممکنه همین فردا یه نسخه جدید بده و این روش دیگه کمکی نکنه. (البته بعیده به این زودی‌ها این اتفاق بیفته، ولی خب امکانش هست)این روش به عنوان یکی از روش‌هایی هست که میشه برای Decrypt ترافیک ازش استفاده کرد. (تنها روش نیست و ممکنه نسبت به شرایط پروژه، روش‌‌های دیگه سریع‌تر به نتیجه برسن)راستی تا یادم نرفته بگم که تو این پست که حدودا برای دو سال پیش میشه، گفته بودم برای Proxy کردن می‌تونین یا از ProxyDroid استفاده کنید یا از iptables، ولی تو نسخه‌های اخیر فلاتر ProxyDroid تو خیلی موارد دیگه جوابگو نیست و بهتره مستقیم از iptables استفاده کنین. روش کار رو هم که تو همون پست توضیح دادم.مثلا اینجا گفتیم ترافیک 80 و 443 رو بفرست به 192.168.1.4:8080 که میشه همون Proxy Serverمونمورد دوم هم اینکه، برای Bypass کردن SSL Pinning اپلیکیشن‌های فلاتری، دیگه لازم نیست روش وقت‌گیر قبلی که تو همون پست توضیح دادم رو انجام بدین، می‌تونین از اسکریپت زیر استفاده کنین که کار رو واقعا ساده کرده:https://github.com/NVISOsecurity/disable-flutter-tls-verificationالبته روشی که این Script استفاده میکنه چیز خاص و جدیدی نیست، میاد بر اساس Pattern، بخش مربوط به Pinning رو پیدا و Hook می‌کنه. (در واقع همون روشی که ما تو اون پست به صورت دستی انجام می‌دادیم رو به صورت automate انجام میده و همین باعث شده کار بسیار سریع‌تر بشه)خب دیگه توضیح کافیه، بریم سر اصل کار 🤠فرض کنین یک اپلیکیشن فلاتری داریم که در کنار پیاده‌سازی کل مکانیزم‌هایی که بالاتر اشاره شد، ترافیک‌ش رو هم به‌صورت Encrypt شده ارسال و دریافت می‌کنه، مثلا چیزی شبیه به این:تا قبل از انتشار ابزاری که می‌خوام معرفی کنم، حل این چالش وقت زیادی می‌گرفت. یک روش این بود که با استفاده از Frida Stalker، بررسی Call stack بعد از ارسال ترافیک، Memory Mapping و یا Debug کردن، محدوده آدرس مشکوک رو پیدا می‌کردی، بعد با Disassembler بین اون محدوده Callersها رو بررسی می‌کردی و یکی یکی همه رو Hook می‌کردی تا در نهایت بخش اصلی پیدا بشه.البته ناگفته نمونه از اونجایی که اکثر اپلیکیشن‌ها از الگوریتم‌های رایج (کپی از اینترنت) استفاده می‌کنن، کلی اسکریپت عمومی Frida برای این منظور وجود داره که بر اساس Pattern بخش مورد نظر رو Hook می‌کنه.خودمون هم وقتی یک بار بخش مرتبط با Encryption رو پیدا می‌کنیم، می‌تونیم pattern‌ در بیاریم و برای پروژه‌های بعدی به امید اینکه شاید از همون الگوریتم استفاده کرده باشه، ازش استفاده کنیم. ولی خب یه تغییر کوچیک تو الگوریتم‌ رمزنگاری باعث میشه این Patternها دیگه match نشن و مجبور باشیم مراحلی که بالا اشاره کردم رو دستی بریم جلو.(در خصوص استخراج Pattern و اسکریپت نوشتن برای Frida تو پست‌های قبلی توضیح دادم)برای حل این چالش، Blutter همون ابزاریه که می‌تونه تا حد زیادی این فرایند رو برامون سریع‌تر کنه:https://github.com/worawit/blutterفقط در نظر داشته باشین که:پیشنهاد می‌کنم روی لینوکس یا macOS ازش استفاده کنید، سر ویندوز آخرین باری که تست کردم مشکل زیاد داشت.نسخه‌ و توزیعی از لینوکس که استفاده می‌کنید باید حداقل GCC 13 داشته باشه (من پیشنهادم استفاده از آخرین نسخه Kali هست، چون هم پیش‌نیازها و هم یکسری از ابزارهایی که نیاز داریم رو از قبل نصب داره)این ابزار در واقع Object Pool و Snapshotها رو Parse می‌کنه و مقادیر رو به همراه Offset نمایش میده.از اونجایی که فرمت Snapshot و Object Pool بین نسخه‌های مختلف Dart فرق می‌کنه، فایل رو که بهش میدین، اگه اون نسخه از Dart رو نداشته باشه اول دانلودش می‌کنه. (IPتون نباید ایران باشه)فعلا فقط ARM64 پشتیبانی می‌شه.استفاده‌ ازش خیلی ساده‌ست، فقط کافیه مسیر lib/arm64-v8a/libapp رو بهش بدین:python3 blutter.py path/to/app/lib/arm64-v8a out_dirبه این صورت:حالا اگه فولدری که به‌عنوان خروجی (out_dir) مشخص کردیم رو چک کنید، این چند فایل داخلش ایجاد شده:asm/ : بخش‌های اسمبلی استخراج شده به همراه symbols برای تحیل در Disassemblerها (تو حالتی که میزان Obfuscation شدید باشه، خیلی کاربردی نیست)blutter_frida.js : اسکریپت‌ Frida مورد نیاز برای ادامه کارida_script/ : اضافه کردنش به IDA باعث خواناتر شدن Disassembly میشه. بر اساس دیتایی که استخراج کرده، فانکشن‌ها رو نامگذاری می‌کنه، کامنت اضافه می‌کنه، label میذاره و...objs.txt : ساختار کامل و درختی از Objectها به همراه referenceشونpp.txt : لیست همه Objectهای موجود در Object Pool (مهم‌ترین فایلی که الان باهاش کار داریم)پس از بین فایل‌هایی که ایجاد کرده، کار رو از pp.txt شروع می‌کنیم تا بتونیم اون قسمت از کد که مربوط به Encryption میشه رو پیدا کنیم. این فایل تو حالت کلی همچین ساختاری داره:ساختار کلی فایل pp.txtاگه پست‌های قبلی و مخصوصا دوره کرک رو دیده باشید، می‌دونید که توی همچین حالتی میریم سراغ نشونه‌هایی که بتونه ما رو به بخشی که دنبالش هستیم نزدیک‌تر کنه. تو این وضعیت چی قابل‌اتکا و یونیک‌تره؟ما یه اپ داریم که بخش Body در Request/Response به صورت Encrypt شده به وب‌سرور ارسال/دریافت میشه. پس می‌تونیم همچین Flow ای رو براش در نظر بگیریم:بخش Request:بخش Body از Request، به صورت plaintext به عنوان آرگومان ورودی به یک فانکشن پاس داده میشه و مقدار Encrypt شده Return میشه.درخواست نهایی به همراه مقدار تولید شده از مرحله قبل (Body رمز شده) به وب سرور ارسال میشه.بخش Response:بخش Body از Response، به فانکشن Decryption پاس داده میشه.خروجی مرحله قبل (دیتای Decrypt شده) به فرمتی مثل JSON تبدیل میشه.از این Flow فرضی چه پارامترهایی رو داریم؟ در مرحله‌ی قبل تونستیم ترافیک رو با Proxy بگیریم، درسته که بخش Body رمز شده بود، اما این مقادیر رو داریم:Scheme, Hostname, Port, Path, Params, Method, Headersاز بین این پارامتر‌ها، اونی که بیشتر از بقیه می‌تونیم روی یونیک بودنش حساب کنیم، مقدار Hostname هست. فرض کنید hostname برابر با crypto-feed.example.net باشه، پس می‌تونیم خروجی pp.txt رو با این مقدار فیلتر کنیم.نتیجه به این صورت میشه:فیلتر کردن بر اساس مقدار hostname (با 3 خط قبل و بعدش)همون‌طور که مشخصه، تونستیم مقدار hostnameهمراه با offsetش که برابر با 0xc800 هست رو پیدا کنیم.اگه دقت کنید مقدار hostnameهمراه با مقادیر مرتبط دیگه مثل apiKey و Content-Type کنار هم قرار گرفتن. البته ترتیب قرارگیری مقادیر در Object Pool همیشه دقیقا همون ترتیب استفاده‌شون در کد نیست، ولی معمولا نزدیک به هم ذخیره میشن و همین می‌تونه یه نشونه بده که مسیر رو درست اومدیم.اگه Object Pool رو مثل یک جدول دو‌ ستونه در نظر بگیریم، ستون اول شماره ردیف (offset) و ستون دوم مقدار متناظر اون ردیف میشه. ما الان شماره ردیف و مقدارش رو داریم، قدم بعدی اینه که ببینیم در کد دقیقا کجا از این offset استفاده شده. پس نیاز داریم با نحوه دسترسی و استفاده از Object Pool، مفاهیم پایه ARM64 و قراردادهای داخلی Dart VM آشنا باشیم. ولی نگران نباشید، برای چیزی که ما اینجا لازم داریم نیاز به دانش عمیقی نیست و هر نکته‌ای نیازه رو تو همین پست توضیح میدم.برای یادگیری سریع‌تر معماری ARM، پیشنهاد می‌کنم اول با معماری ساده‌تری مثل x86 شروع کنید و مفاهیم پایه‌ رو یاد بگیرید. بعد در خصوص تفاوت‌هایی که دارن مطالعه کنید، اینجوری درک ساختار ARM خیلی راحت‌تر و سریع‌تر میشه.در معماری ARM64، خوندن مقادیر از حافظه می‌تونه به روش‌های مختلفی انجام بشه، اما در باینری‌های Flutter معمولا اکثر وقت‌ها با این دو دستور متوالی زیر انجام می‌شه.مثلا فرض کنید می‌خوایم مقداری با offset = 0x95a7 رو بخونیم:یا همون مقداری که بالاتر داشتیم (hostname) با offset = 0xc800 رو بخوایم تو کد استفاده کنیم:در واقع، این دو خط کنار هم یک الگوی مشخص از دسترسی غیر مستقیم به یک مقدار در حافظه (از بخش Object Pool) رو تشکیل میدن که در باینری‌های Flutter و Dart AOT بسیار رایجه.حالا بریم ببینیم این دو خط دقیقا چکار می‌کنن:در معماری ARM64، دستور add به صورت پیشفرض فقط می‌تونه تا مقدار 12 بیت (0 تا 4095) رو به‌ صورت مستقیم نگه داره، مثلا اگه بگیم:add x0, x1, #0xFFFاین مجازه و مشکلی نداره، ولی اگه بخوایم از مقدار بزرگتری مثل 0xC800 استفاده کنیم، چون از محدوده‌ی 12 بیتی خارج میشه دیگه به این صورت نمیشه انجامش داد. برای حل این محدودیت (البته محدودیت که نه، ساختارش به این صورته)، از شیفت 12 بیتی (lsl 12) استفاده می‌کنیم. اینجوری می‌تونیم مقدار اصلی رو در محدوده 12 بیت، مثل 0xC بدیم و بعد با lsl 12 اون رو 12 بیت (یعنی 2 به توان 12 که میشه 4096 برابر) شیفت بدیم تا در واقع مقدار 0xC000 ساخته بشه.بعد با دستور ldr مقدار 0x800 بهش اضافه می‌کنیم تا به آدرس نهایی 0xC800 برسیم.خلاصه و ساده‌ش اینجوری میشه که جای اینکه مستقیم از 0xC800 بخونیم، به دلیل ساختار ARM64، اول0xC000 رو می‌سازیم و بعد 0x800 رو بهش اضافه می‌کنیم تا در نهایت بشه همون 0xC800حالا این وسط x27 و x16 کارشون چیه؟x27 طبق قرارداد، برای اشاره به Object Pool استفاده میشه و مقدارش در طول اجرای کد ثابت باقی می‌مونه.x16 در ARM64 یک رجیستر موقت (Temporary Register) هست که برای محاسبات موقت استفاده میشه. البته کامپایلر می‌تونه برای این منظور از x17 یا رجیسترهای دیگه هم استفاده کنه.در x86_64، دسترسی به Object Pool با استفاده از R15 انجام میشه و در ARM32 با استفاده از R5،ولی ما این پست رو بر اساس ARM64 میریم جلو و به سایر معماری‌ها کاری نداریم.پس اگه بخوایم این قسمت رو جمع‌بندی کنیم:قسمتی که با کادر قرمز مشخص شده، نسبت به offset تغییر می‌کنه.بخش‌هایی که با کادر سبز مشخص شدن، فعلا ثابت هستن.مقدار x16 که با کادر زرد مشخص شده، معمولا ثابته ولی ممکنه از رجیستر دیگه‌ای مثل x17، x1 و... هم استفاده بشه.چرا ممکنه تغییر کنه یا فعلا ثابته؟ چون در حال حاضر—طبق قرارداد و ساختار فعلی—مثلا x27 برای اشاره به Object Pool استفاده می‌شه، یا مثلا برای استفاده از مقادیر بزرگ‌تر از 12بیت از lsl 12 استفاده می‌کنیم، ولی ممکنه همین فردا نسخه جدیدی منتشر بشه و این ساختار کامل تغییر کنه، مثلا از یک رجیستر دیگه برای اشاره به Object Pool استفاده بشه یا حتی کل روش فراخوانی عوض بشه.البته احتمال همچین تغییری کمه. ولی از اونجایی که فلاتر تا الان تغییرات کم نداشته، پس هر تغییری امکان‌پذیره. بریم جلوتر یک نمونه‌ از این تغییرات رو می‌بینیم.حالا که با نحوه فراخوانی آشنا شدیم، برای ادامه‌ی کار باید این بخش از کد رو در فایل باینری پیدا کنیم.برای این منظور، Radare2 یکی از بهترین Disassemblerهایی هست که می‌تونیم ازش استفاده کنیم، چون به ما امکان Pattern Search می‌ده و برای این نوع جست‌وجو نیازی به نصب هیچ پلاگینی نداره (حتی توی IDA Pro هم نیازه پلاگین جداگونه نصب کرد).البته اگه کار با رابط گرافیکی براتون راحت‌تره، می‌تونید از Cutter هم استفاده کنید که در واقع رابط گرافیکی Radare2 محسوب می‌شه.طبق مستندات radare2، برای جستجو در اسمبلی به روش Pattern Search می‌تونیم به این صورت عمل کنیم:/ad &lt;pattern&gt;به فرض اگه بگیم:/ad/ add.*, x27, 0xc, lsl 12;0x800]یعنی داریم میگیم برو هر جا add.*, x27, 0xc, lsl 12 دیدی که خط بعدش هم 0x800بود، بهمون نمایش بده. حالا اینو شما میتونین به هر مدل دیگه‌ای که نیازه تغییرش بدین، مثلا اگه جای x27 خواستین بگین کل رجیسترها رو بگرده، میشه به این صورت:/ad/ add.*, x[0-9]+, 0xc, lsl 12;0x800]پس بعد از باز کردن فایل lib/arm64-v8a/libapp.so در Radare2، مقدار مورد نظرمون رو سرچ می‌کنیم.اینجوری آدرسی از باینری که مقدار hostname رو فراخوانی کرده پیدا می‌کنیم:حالا اگه به محدوده این آدرس بریم، سایر مقادیر و پارامترهایی که در Object Pool کنار هم قرار داشتن رو هم می‌تونیم ببینیم:در واقع هر وقت این الگو رو دیدین، کافیه مقدار offsetش رو داخل فایل pp.txt پیدا کنین تا مقدار اصلیش مشخص بشه (مطابق تصویر بالا و پایین)تا الان محدوده آدرس مرتبط با Encryption رو پیدا کردیم، ولی هنوز محل دقیقش رو نمی‌دونیم. اینجا نیاز داریم با Calling Convention در Flutter آشنا بشیم:تا قبل از نسخه 3.4.0، زبان Dart عملا هیچ Convention ثابت و استانداردی برای نحوه مقداردهی آرگومان‎‌های توابع نداشت. یکسری رو می‌ریخت داخل استک و یکسری رو توی رجیسترها ذخیره می‌کرد. همین موضوع باعث شده بود حتی IDA Pro گیج بشه و نتونه ساختار فانکشن‌ها رو به صورت صحیح نمایش بده. (اون زمان JEB عملکرد خیلی بهتری داشت)اما از v3.4.0 به بعد، استانداردهای ARM64 رعایت شد و بر این اساس 8 آرگومان اول هر تابع در رجیستر‌های x0 تا x7 ذخیره میشن و اگه تعداد آرگومان‌ها بیشتر باشه در Stack قرار می‌گیرن. (البته رجیستر x0 علاوه بر نقش آرگومان اول، برای نگه‌داری مقدار return هم استفاده میشه)حالا که با Calling Convention آشنا شدیم، برمی‌گردیم به کد بالا، همونطور که در تصویر زیر مشخص شده ما اینجا 3 فانکشن داریم:یکی از این فانکشن‌ها، مقادیر قبلیش (hostname و سایر پارامترها) را به عنوان ورودی می‎‌گیره و به مرحله بعدی (رمزنگاری، ارسال درخواست و ...) ارسال می‌کنه. پس برای اینکه بتونیم به بخش Encryption نزدیک‎‌تر بشیم نیاز داریم این فانکشن رو پیدا کنیم. ولی چجوری؟خبر خوب اینکه اکثر وقت‌ها برای پیاده‌سازی Encryption از روش‌های رایج استفاده میشه و معمولا با چیز عجیب غریبی روبرو نیستیم. تو همچین حالتی میایم آرگومان‌های ورودی یا مقدار return شده‌ی فانکشن‌هایی که بلافاصله بعد از فراخوانی پارامترهایی مثل hostname قرار دارند رو با Hook کردن Log می‌کنیم. (اکثر وقت‌ها این روش جواب میده)ممکنه این سوالات براتون پیش بیاد:تا چند فانکشن بعدی رو بررسی کنیم؟ معمولا حداکثر تا 10 فانکشن بعدی کافیه. (پیدا کردنش سخت نیست اصلا)اگه اختصاصی پیاده‌سازی شده باشه چطور؟ روش همونه، ولی ممکنه نیاز به تحلیل کد پیدا کنیم و این باعث میشه وقت بیشتری بگیره.لازمه کد رو خط‌به‌خط تحلیل کنیم؟ ترکیب تحلیل استاتیک و داینامیک، قطعا همیشه بهترین انتخابه. ولی واقعیت اینجاست در مبحث Bypass مکانیزم‌های رایج امنیتی با فرض اینکه از الگوریتم و روش خیلی خاصی استفاده نشده باشه، اکثر وقت‌ها خیلی اونقدری نیاز به تحلیل عمیق کد پیدا نمی‎‌کنیم. مثلا اینجا خروجی فانکشن اول در [x29, -0x18] قرار گرفته (آدرس 0x0040997c) و بعد در خط 0x00409990، این مقدار به‌ عنوان آرگومان اول به فانکشن سوم پاس داده میشه. پس احتمالا فانکشنی که دنبالشیم همین فانکشن سوم باشه. (این فقط یک احتمال منطقیه و ممکنه درست نباشه، دقیق‌ترین روش اینه که مقدار x1 یا [x29-0x18] رو با یه Hook کردن ساده Log بگیریم.)اسکریپتی که برای Hook کردن این فانکشن‌ها نیاز داریم رو از قبل Blutter با نام blutter_frida.js ایجاد کرده. کافیه فانکشن onLibappLoaded از این فایل رو Edit کنیم و آدرس فانکشن مد نظرمون رو به همراه شماره (index) آرگومانی که می‌خوایم Log کنه رو بهش بدیم، مطابق تصویر:اگه با نوشتن اسکریپت برای Frida آشنایی ندارید، پیشنهاد میکنم پست‌های مرتبط قبلی رو مطالعه کنید.همونطور که مشخصه آرگومان اول این فانکشن ظاهرا همون Body ای هست که قراره در مرحله بعد Encrypt بشه:اگه وارد این فانکشن (fcn.0029c15c) بشیم، همونطور که می‌بینید داخلش کلی فانکشن دیگه وجود داره. تا اینجا مشخص شده که این فانکشن، مقدار Body رو به عنوان ورودی می‌گیره، بنابراین به احتمال خیلی زیاد یکی از این فانکشن‌های داخلی، باید همون Encryption باشه. ولی کدوم یکی؟یک روش می‌تونه این باشه که مثل قبل، با دنبال کردن الگوهای فراخوانی از Object Pool و پیدا کردن Offsetها در فایل pp.txt، فانکشن‌های مشکوک رو جدا کنیم و بعد با Hook کردن تک تک‌شون به فانکشنی که دنبالش هستیم برسیم.یا میتونیم مقدار Return فانکشن‌ها رو Log کنیم. طبیعتا فانکشنی که Encryption رو انجام میده، باید مقدار Encrypt شده رو Return کنه.ولی روش خیلی سریع‌تر اینه که برعکس بریم جلو، حالا این یعنی چی؟قبل از ادامه تا یادم نرفته: همیشه وقتی رفتار کلی یک فانکشن رو متوجه شدین، یک اسم براش بذارین. اینجوری هم تحلیل ساده‌تر میشه و هم اینکه بعدا می‌تونین خیلی راحت‌تر به این فانکشن برگردین. پس به این صورت یک اسم بهش اختصاص میدیم:afn encrypt_request_bodyیه نگاه سریع به روش رمزنگاری AES بندازیم. برای کار با AES به چند پارامتر نیاز داریم:Key — کلید رمزنگاریMode — مثل CBC, CTR, GCM و …Padding — وقتی طول دیتا دقیقا مضربی از اندازه بلاک AES نباشه، بلاک آخر به کمک یک روش Padding باید کامل بشه تا به اندازه بلاک برسه.IV / Nonce — مقداری که برای شروع Encryption استفاده میشه تا خروجی هر بار متفاوت باشه.البته ما تو این پست کاری به جزئیات این پارامترها نداریم. توضیحات بالا فقط برای آشنایی کلی اورده شدن.اگه به نمونه کد زیر که برای Decrypt/Encrypt با استفاده از AES هست دقت کنید، پارامترهایی که با کادر قرمز مشخص شدن از اونجایی که به صورت String هستن، احتمال داره بشه داخل pp.txt پیداشون کرد.از بین این پارامتر‌ها، مقدار key و iv که هیچی، درسته که string هستن، ولی اگه مقدارشون رو می‌دونستیم که از همون اول کار تموم بود! می‎‌مونه mode و padding. مقادیری که این دوتا می‌تونن بگیرن زیاد نیست، پس با یه جستجوی ساده توی فایل pp.txt می‌شه پیداشون کرد.البته این دو پارامتر می‌تونن طوری پیاده‌سازی بشن که مستقیم توی pp.txt نباشن، اما این حالت خیلی کم پیش میاد و ما هم فرض می‌کنیم با همچین حالتی مواجه نیستیم.اگه به تصویر زیر که مربوط به بخشی از فایل pp.txt هست دقت کنید، می‌بینید که پارامتر mode برابر با cbc و پارامتر padding برابر با PKCS7 هست. مقدار Offset هرکدوم هم که مشخصه. پس با همون روش pattern search که قبلا انجام دادیم، می‌تونیم اینجا هم محل فراخوانی این پارامترها رو پیدا کنیم. جایی که این پارامترها استفاده شدن، به احتمال زیاد همون تابع اصلی Decrypt/Encryptه.سرچ رو بر اساس مقدار mode میریم جلو، یعنی 0xc8b0 ، که میشه به این صورت:طبق خروجی بالا، این مقدار دوبار توی کد فراخوانی شده، که منطقیه — یک بار برای Decrypt و بار دیگه برای Encrypt.حالا اگه به آدرس 0x0026fda4 بریم، به این فانکشن می‌رسیم:همونطور که مشخصه (کادر زرد)، این فانکشن از دو جا call شده که یکی از اون‌ها همون encrypt_request_bodyه که مرحله‌ی قبل بهش رسیده بودیم. اینم کاملا منطقیه، چون توی encrypt_request_body باید یک فانکشن صدا زده بشه که مقادیر رو بگیره و Encrypt کنه. بنابراین مسیر رو درست اومدیم و فانکشنی که الان داخلش هستیم وظیفه Encrypt کردن رو داره. اون یکی فانکشن، یعنی fcn.0026fa18 هم به احتمال زیاد فانکشن مرتبط با Decrypt باشه.خب حالا برای راحتی بیشتر میایم به این فانکشن هم یک اسم میدیم، مثلا :afn AES_Encryptorحالا اگه به encrypt_request_body برگردیم، فانکشن AES_Encryptor با اسمی که براش تعیین کردیم نمایش داده شده و به این روش تونستیم خیلی راحت و سریع، فانکشنی که وظیفه Encrypt کردن داره رو پیدا کنیم:حالا چه کار کنیم؟ درست حدس زدید — آرگومان‌های ورودیش رو بررسی می‌کنیم. طبق Calling Convention اینجا دو آرگومان داریم: x1 و x2. کدومو بررسی کنیم؟ هر دو رو — البته با خوندن کد می‌شه فهمید کدوم حساس‌تره، ولی تو این پست در کل هدف اینه تا جای ممکن وقت سر تحلیل کد نذاریم.در نهایت با Hexdump گرفتن از آرگومان دوم (x2) به اندازهٔ 500 بایت، می‌تونیم به key برسیم:چرا 500 بایت؟باید طولی رو در نظر بگیریم که کل این محدوده پوشش داده بشه و هیچ دیتای احتمالی‌ای از دست نره. البته 500 معمولا عدد بزرگیه، ولی خیالمون راحته دیتایی از دست نمیره.اگه با روش زیر آشنایی ندارید، در پست‌های قبلی توضیح دادم.خروجی به صورت زیر خواهد بود:key: 123456789012345678901234تا اینجا تونستیم مقدار پارامتر key و mode رو پیدا کنیم، ولی برای Decrypt کردن نیاز داریم مقدار یک پارامتر دیگه رو هم بدونیم که اون iv هست.دقت کنید که پارامتر iv نباید ثابت باشه، ولی معمولا برای راحتی کار ثابت در نظر گرفته می‌شه. پس وقتی به iv رسیدین، حتما چک کنین ببینین توی هر درخواست مقدارش تغییر می‌کنه یا ثابته.اگه فرض کنیم توسعه‌دهنده از کتابخونه و الگوریتم‌های رایج استفاده کرده باشه، پارامتر iv معمولا بعد از ایجاد Object از کلاس AES (بعد از Constructor) مقدار دهی می‌شه. طبق نمونه کد زیر:بنابراین، اگه به encrypt_request_body برگردیم، پارامتر iv باید در یکی از این فانکشن‌ها مقداردهی بشه:حالا کدوم فانکشن؟ یک روش اینه که آرگومان‌های ورودی چند فانکشن‌ بعدی رو دونه‌دونه چک کنیم. این راه در نهایت جواب میده ولی اگه 20 تا فانکشن داشتیم چطور؟ یکی یکی همه رو Hook کنیم!؟ پس بهتره یکم دقیق‌تر بریم جلو.اگه به نمونه کد بالا دقت کنید، در پکیج encrypt در Dart، فانکشنی که پارامتر iv رو مقداردهی می‌کنه، مقدار plaintext رو هم به عنوان ورودی گرفته. پس به جای هوک کردن همه فانکشن‌ها، فقط سراغ اونایی میریم که در آرگومان‌های ورودی‌شون plaintext رو هم دارن، این باعث میشه تعداد فانکشن‌هایی که باید بررسی کنیم خیلی کمتر بشه. حالا سؤال: plaintext رو از کجا پیدا کنیم؟اگه یادتون باشه موقعی که encrypt_request_body فراخوانی می‌شد، x1 همون مقداری بود که به‌ عنوان plaintext به عنوان آرگومان ورودی پاس داده می‌شد. این پارامتر طبق Calling Convention میشه آرگومان دوم و اگه محل فراخوانی هم بررسی کنیم، radare2 هم این موضوع رو برامون مشخص کرده:پس کافیه بریم داخل فانکشن و بررسی کنیم که آرگومان دوم به چه صورت و کجا استفاده شده:همونطور که مشخصه آرگومان دوم در آفست 8 از Stack Pointer قرار گرفته که به صورت [8- , x29] نمایش داده میشه. پس باید بگردیم فانکشن‌هایی رو پیدا کنیم که [8- , x29] رو به عنوان آرگومان ورودی دارن. می‌رسیم به fcn.0029c118 و fcn.0029c254 :اینجا کلا 5 فانکشن داشتیم و 3تاشون حذف شد، ولی گاهی پیش میاد که تعداد فانکشن‌ها خیلی زیاده و با همین روش ساده میشه تعدادشونو تا حد قابل توجهی کم کرد.در نهایت، با Hook کردن آرگومان x3 در فانکشن دوم (fcn.0029c254)، به مقدار iv می‌رسیم:خروجی به این صورت میشه:الان تموم پارامترهای مورد نیاز برای Decrypt رو داریم: 😍algorithm: AES
mode: CBC
key: 123456789012345678901234
iv: abcdefghijklmnopحالا کافیه با هر ابزار یا روشی که براتون راحت‌تره مثل CyberChef مقدار مورد نظرتون رو Decrypt کنین:البته در فرایند تست نفوذ، ما به Intercept ترافیک نیاز داریم و انجام دستی Encrypt/Decrypt روش مناسبی نیست. تو این حالت اگه با Burp Suite کار می‌کنین، خیلی راحت می‌تونین یه Extension براش آماده کنین، الان با کمک AI این موضوع به سادگی و خیلی سریع قابل انجامه. یا اگه Bug Bounty کار می‌کنین، پیشنهاد می‌کنم یه ابزار GUI ساده برای این فرایند آماده کنین. آماده کردن همچین چیزایی خیلی وقت‌ها می‌تونه باعث Bonus بشه.خلاصه اینکه وقتی پارامترهای Encrypt/Decrypt رو پیدا کردین، از اینجا به بعد با توجه به پروژه و هدف‌تون بهترین روش رو برای ادامه کار انتخاب کنین.خب بالاخره این پست هم تموم شد 😍امیدوارم براتون مفید بوده باشه.اگه سوالی داشتید می‌تونیم لینکدین در ارتباط باشیم.شاد و موفق باشید... ❤️</description>
                <category>عماد عابدینی</category>
                <author>عماد عابدینی</author>
                <pubDate>Tue, 28 Oct 2025 10:33:13 +0330</pubDate>
            </item>
                    <item>
                <title>دوره SANS FOR610: Malware Analysis</title>
                <link>https://virgool.io/@emad_abedini/%D8%AF%D9%88%D8%B1%D9%87-sans-for610-malware-analysis-iszov7giaxfq</link>
                <description>اگه وقت یا حوصله خوندن PDF اصلی FOR610 (حدود 1000 صفحه) رو ندارین، در فایل زیر کل محتوای دوره به زبان ساده‌تر و در 179 صفحه جمع‌بندی شده.این فایل در واقع یادداشت‌های شخصی من از این دوره بود که چند روز پیش به نظرم رسید مرتبش کنم و به صورت عمومی منتشر کنم تا شاید بتونه مفید باشه.متن هیچ ارتباطی با PDF اصلی دوره نداره (کپی یا ترجمه نیست)همه مباحث، ابزارها و حتی کوچیک‌ترین نکات و اصطلاحات دوره، با بیان ساد‌تر و خلاصه‌تر در این فایل اورده شده.برای خیلی از بخش‌ها، شماره صفحه PDF اصلی هم ذکر شده تا در صورت نیاز به مطالعه عمیق‎‌تر، بتونین راحت‌تر به منبع اصلی رجوع کنید. (البته به جز 3-4 مورد، فکر نمی‌کنم نیازی به این کار باشه چون تقریبا همه‌ چیز توضیح داده شده)این فایل قطعا نمی‌تونه جایگزین PDF اصلی باشه، ولی اگه با مباحث پایه‌ای و مقدمات تحلیل بدافزار آشنایی دارید، واقعا نیازی به خوندن 1000 صفحه نیست، تموم مطالب تو همین 179 صفحه کامل پوشش داده شده.فایل رو می‌تونین از کانال تلگرام زیر دانلود کنید:https://t.me/ThreatStrixامیدوارم مفید باشه ❤️</description>
                <category>عماد عابدینی</category>
                <author>عماد عابدینی</author>
                <pubDate>Thu, 18 Sep 2025 14:52:29 +0330</pubDate>
            </item>
                    <item>
                <title>استخراج Shellcode و فایل‌های Pack شده از بدافزارها</title>
                <link>https://virgool.io/@emad_abedini/%D8%A7%D8%B3%D8%AA%D8%AE%D8%B1%D8%A7%D8%AC-shellcode-%D9%88-%D9%81%D8%A7%DB%8C%D9%84-%D9%87%D8%A7%DB%8C-pack-%D8%B4%D8%AF%D9%87-%D8%A7%D8%B2-%D8%A8%D8%AF%D8%A7%D9%81%D8%B2%D8%A7%D8%B1%D9%87%D8%A7-xtyn2k5nifo0</link>
                <description>فرایند استخراج Shellcode و فایل‌‌های Pack شده از بدافزارهایی که در زمان اجرا بخشی از کد رو Unpack می‌کنن (مثل Dropperها، Packed Malwareها یا Multi-Stageها)، نسبت به روش پیاده‌سازی و مکانیزم‌های به کار رفته، می‌تونه بسیار زمان‌بر و پیچیده باشه.پیش‌نیاز این پست اینه که با مقدمات Reverse Engineering و Malware Analysis آشنا باشید.وقتی با این دسته از بدافزارها سر و کار داریم، دو روش اصلی برای استخراج بخش‌ Pack شده وجود داره:۱. اجرای بدافزار و بررسی Memory:بدافزار رو اجرا می‌کنیم و هم‌زمان میریم سراغ Regionهایی از Memory که Execute Flag دارن. بخش‌ Unpack شده ممکنه توی همین Regionها قابل شناسایی باشه.۲. زیر نظر گرفتن کردن APIهای مرتبط (روش دقیق‌تر):در این روش، توابعی که کارشون تخصیص یا تغییر Permission در Memory هست رو زیر نظر می‌گیریم، تا هرجا بدافزار روی Memory چیزی نوشت یا تغییری ایجاد کرد متوجه بشیم.تعدادی از توابع مهمی که باید حواسمون بهشون باشه، ایناست:VirtualAlloc / VirtualAllocExVirtualProtect / VirtualProtectExNtAllocateVirtualMemoryNtProtectVirtualMemoryمثلا طبق مستندات، در تابع VirtualAlloc آرگومان اول (lpAddress) همون آدرس حافظه‌ایه که قراره فضا بهش اختصاص داده بشه. ما این آدرس رو زیر نظر می‌گیریم تا هر موقع هر بخشی از Code خواست چیزی روش بنویسه یا ازش بخونه، دیباگر همون لحظه Break کنه و اینطوری قدم‌به‌قدم به بخش Unpack شده نزدیک‌تر بشیم.البته ناگفته نمونه که lpAddress می‌تونه NULL هم باشه. در واقع با این پارامتر فقط یک آدرس پیشنهادی مشخص میشه، اگه NULL بدیم یا اون آدرس قبلا گرفته شده باشه، سیستم یک آدرس دیگه اختصاص می‌ده.روش دقیق‌تر اینه که بذاریم VirtualAlloc کامل اجرا بشه و Breakpoint رو روی مقدار برگشتی (Base Address) بذاریم.ازونجایی که این مباحث موضوع این پست نیست، وارد این جزئیات نمی‌شیم.در هر دو روش بالا، اصل کاری که انجام میدیم اینه که بذاریم بدافزار روال طبیعی خودش رو طی کنه تا فرایند Unpack انجام بشه، بعد به یکی از این دو روش کارو ادامه بدیم:از Memory دامپ می‌گیریم و در صورت نیاز ساختار فایل، OEP (Original Entry Point) و IAT رو بازسازی (Fix) می‌کنیم و در نهایت فایل ایجاد شده رو جداگونه تحلیل می‌کنیم.بخش Unpack شده رو مستقیم توی دیباگر تحلیل می‌کنیم.البته واقعیت اینه که این فرایند خیلی وقت‌ها ساده پیش نمیره و می‌تونه زمان زیادی از ما بگیره، مثلا:بعضی بدافزارها بخش Unpack شده رو در چند مرحله (Multi-Stage) و مستقیم در Memory اجرا می‌کنن، در واقع مرحله اول فقط Stage بعدی رو لود می‌کنه.ممکنه به یک Region از حافظه فلگ‌هایی مثل PAGE_GUARD یا PAGE_NOACCESS بده، تو این حالت وقتی کد میخواد به اون Region دسترسی پیدا کنه یک Exception اتفاق میفته و همون لحظه دسترسی Execute داده می‌شه.ممکنه شماره‌ی Syscall رو مستقیما فراخوانی کنه و پارامترها رو در رجیسترها قرار بده.ممکنه بعد از Unpack، کد خودش رو در زمان اجرا (Runtime) تغییر بده.ممکنه از مکانیزم‌های پیشرفته تشخیص دیباگر (Anti-Debugging) استفاده کرده باشه.و تکنیک‌های پیچیده‌ی دیگه که باعث شن تحلیل پیچیده‌تر بشه.حالا توی این مسیر می‌شه از ابزارهایی کمک گرفت که فرایند Unpack رو خودشون انجام میدن و فایل‌ Pack‌شده رو خیلی سریع استخراج می‌کنن. 😍البته که قطعا این ابزارها همیشه جواب نمیدن، ولی بازم تست کردنشون قبل از شروع Debug ممکنه بتونه کارمون رو کلی جلو بندازه.یکی از ابزارهای خیلی خوب در این زمینه، mal_unpack هست که توسط Aleksandra Doniec (معروف به hasherezade) و بر پایه PE-sieve توسعه داده شده:https://github.com/hasherezade/mal_unpackنحوه کارش به این صورته که بدافزار رو اجرا می‌کنه و هم‌زمان یه Passive Scan روی Memory انجام میده تا بخش‌هایی که Executable Code جدید لود شده رو پیدا کنه. نکته اینجاست که از API Hooking استفاده نمی‌کنه و بنابراین مکانیزم‌های Anti-Debug و Obfuscation معمولا تاثیری روش ندارن. در واقع، mal_unpack میاد بدافزار رو اجرا می‌کنه، Stageهای جدید رو تشخیص میده، ازشون Dump می‌گیره و در نهایت پروسه رو Terminate می‌کنه.البته بعضی وقتا نمی‌تونه کامل Terminate کنه و اینو تو خروجی گزارش میده.نحوه کار با mal_unpack:کار با این ابزار خیلی ساده‌ست. بعد از دانلود و نصب، به این صورت میشه ازش استفاده کرد:mal_unpack.exe /exe &lt;path_to_the_malware&gt; /timeout &lt;timeout: ms&gt;با exe/ مسیر فایل رو بهش میدیمبا timeout/ مشخص می‌کنیم که فایل چند میلی‌ثانیه اجرا بمونه.mal_unpack.exe /exe C:\samples\malware.exe /timeout 10000خروجی چیزی شبیه به این خواهد بود:اگه دنبال این نمونه از Malwareها هستین، تو Malware Bazaar این تگ‌ها رو سرچ کنید:tag:upxtag:packertag:packedtag:droppertag:loaderبعد از اجرای ابزار، یک فولدر کنار همون فایل اصلی می‌سازه با نام filename&gt;.&lt;extension&gt;.out&gt;. مثلا اگه فایل اصلی malware.exe باشه، خروجی‌ها توی فولدری با نامmalware.exe.out (همونطور که توی لاگ بالا هم مشخصه) ایجاد میشه. محتوای این فولدر چیزی شبیه به این میشه:حالا این فایل‌هایی که ایجاد کرده، دقیقا چی هستن؟فایل‌های json. در واقع خلاصه‌‌ای از کل کاری هست که انجام داده:فایل‌های txt. لیست Importهایی هست که در Memory پیدا کرده:فایل‌های exe. همون Executable Codeهای جدیدی هستن که در Memory لود شدن و ابزار تونسته شناسایی‌شون کنه و Dump بگیره.فایل shc. همون Shellcodeی هست که Dumped down شده روی دیسک.نکات استفاده از Mal_unpack:این ابزار به‌ صورت پیش‌ فرض فقط فایل‌هایی رو Dump می‌کنه که ساختار شبیه به PE دارن. اگه بخوایم Shellcodeهایی که مستقیم در Memory لود میشن یا روی Disk ذخیره میشن رو هم تشخیص بده، باید shellc/ هم اضافه کنیم.در حالت پیشفرض mal_unpack همین که اولین فایل Pack شده رو شناسایی و Dump کنه، ادامه‌ی اجرا رو متوقف می‌کنه. اگه می‌خواید در هر صورت تا پایان مقدار timeout/ به اجرا ادامه بده، باید از trigger T/ استفاده کنید.ممکنه ساختار یا هدرهای PE دستکاری شده باشن. توی این حالت، برای اینکه همچنان بتونه فایل رو شناسایی و Dump کنه، باید hooks/ رو اضافه کنید.اگه فایل dll. باشه، باید به exe/ آدرس rundll32.exe رو بدین و با cmd/ مسیر فایل به همراه فانکشن Entry Pointش رو مشخص کنید.به‌صورت پیش‌فرض، mal_unpack خیلی وقت‌ها نمی‌تونه پروسه رو به‌طور کامل Terminate کنه. برای رفع این مشکل و بهبود Isolation، می‌تونید mal_unpack_drv رو نصب کنید:https://github.com/hasherezade/mal_unpack_drvmal_unpack می‌تونه هر نوع فایلی رو Unpack کنه؟قطعا نه. این ابزار هم مثل سایر ابزارهایی که در زمینه Malware Analysis استفاده میشن، هدفش اینه سرعت فرایندها رو بالا ببره. ولی خب همه این ابزار‌ها در کنار محدودیت‌ها و ضعف‎‌هایی که دارن، تو خیلی از موارد توسعه‌دهنده بدافزار می‌تونه (نسبت به اطلاع از نحوه کار اون ابزار)، کدشو جوری بزنه که این ابزارها کارایی مورد انتظار رو نداشته باشن. مثلا mal_unpackیک مشکل خیلی بزرگی که داشت (الان رفع شده)، این بود که نمی‌تونست Shellcodeهای Cobaltی که بایت اولشون با FC شروع میشه رو تشخیص بده! همین الانم بخش Signature و Patternهایی که داره خیلی کامل نیست.البته Aleksandra (توسعه‌دهنده ابزار) قابلیتی اضافه کرده که بشه به صورت داینامیک، Pattern اضافه کرد. نحوه کار هم خیلی ساده‌ست، بخشی از فانکشن اصلی Shellcode مد نظرتون رو به صورت Raw Hex کپی کنید و به صورت Sig Format با پسوند sig. ذخیره‌ش کنید و در نهایت این فایل رو با پارامتر pattern/ به ابزار بدید، به این صورت:ساختار دقیق SIG Format رو خود Aleksandra تو یک Repository دیگه توضیح داده:https://github.com/hasherezade/sig_finderتو حالتی که با یک بدافزار جدید روبه‌رو باشیم که ندونیم چه نوع Shellcodeی داخلش استفاده شده، طبیعتا قابلیت اضافه کردن Pattern خیلی کاربردی نیست.اگه بخوایم یه جمع‌بندی داشته باشیم، در حال حاضر تموم ابزارهایی که برای این منظور استفاده می‌شن هر کدوم محدودیت‌های خودشون رو دارن و در بهترین حالت روی بعضی نمونه‌ها Fail می‌شن. تنها روشی که فعلا همیشه جواب میده همون Debug کردنه، درسته ممکنه زمان زیادی بگیره، ولی وقتی ابزارها نتونن کمکی کنن، آخرین و مطمئن‌ترین روش همینه.امیدوارم این پست براتون مفید بوده باشه.اگه سوالی داشتید می‌تونیم لینکدین در ارتباط باشیم.شاد و موفق باشید... ❤️</description>
                <category>عماد عابدینی</category>
                <author>عماد عابدینی</author>
                <pubDate>Fri, 22 Aug 2025 16:49:00 +0330</pubDate>
            </item>
                    <item>
                <title>پیاده‌سازی ارتباط بین Ghidra و Local LLM</title>
                <link>https://virgool.io/@emad_abedini/%D9%BE%DB%8C%D8%A7%D8%AF%D9%87-%D8%B3%D8%A7%D8%B2%DB%8C-%D8%A7%D8%B1%D8%AA%D8%A8%D8%A7%D8%B7-%D8%A8%DB%8C%D9%86-ghidra-%D9%88-local-llm-ewyn0lp2bn3j</link>
                <description>توی این پست می‌خوایم با استفاده از (Model Context Protocol) MCP و یک مدل زبانی آفلاین (Local LLM)، سرعت کارمون رو در زمینه Static Analysis تا حد قابل‌ توجهی بالا ببریم.پیش‌نیاز این پست اینه که با مقدمات Reverse Engineering و Static Malware Analysis آشنا باشید.همون‌طور که می‌دونید، تحلیل باینری (به‌ خصوص بخش Static Analysis)، در کل فرایندی زمان‌بره. قبل از AI، سعی می‌کردیم با استفاده از ابزارها و اسکریپت‌ها این فرایند رو تا جای ممکن کوتاه و بخش‌هایی از کار رو Automate کنیم.حالا با وجود AI، در کنار استفاده از ابزارها و اسکریپت‌های قبلی، می‌تونیم سرعت کار رو تا حد زیادی بیشتر از قبل کنیم. البته در مقایسه با حوزه‌هایی مثل Development، هنوز AI تو زمینه Reverse و تحلیل باینری خیلی اونقدری که انتظار میره پیشرفت نکرده! ولی با این وجود، بازم می‌تونه تا حد قابل توجهی کار رو سریع‌تر کنه. (توی Development که دیگه واقعا کم مونده با کارفرما تماس بگیره بگه «پروژه آمادست!» 😄)بگذریم، بریم سر اصل موضوع...1- دانلود و نصب Ghidraاولین چیزی که نیاز داریم، خود Ghidra هست، اگه نصب ندارین، آخرین نسخه رو از GitHub دانلود کنید:https://github.com/NationalSecurityAgency/ghidraیادتون نره که JDK هم باید نصب باشه، Ghidra بهش نیاز داره.2- نصب Ollamaبرای استفاده از یک مدل زبانی آفلاین (Local LLM)، نیاز به یک Engine داریم که مدل رو اجرا کنه. در حال حاضر ابزارها و Engineهای مختلفی برای این منظور وجود داره، ولی ما کارمون را با Ollama میریم جلو.چرا Ollama ؟ 🤔رابط کاربری ساده، نصب و راه‌اندازی سریع، مدیریت همزمان چند مدل، پشتیبانی از API، مستندات کامل و...البته استفاده از Ollama اجباری نیست، شما میتونین از ابزار و Engineهای دیگه هم استفاده کنید.برای دانلود Ollama، نسخه متناسب با سیستم‌عامل خودتون رو از لینک زیر دانلود و نصب کنید:https://ollama.com/downloadنیازه که IP رو از ایران، به جای دیگه‌ای تغییر بدین 😒بعد از نصب، برای اطمینان از اینکه فرایند نصب به درستی انجام شده، ollama رو در Command Line اجرا کنید، خروجی باید به این صورت باشه:3- دانلود مدل زبانی (LLM)حالا باید نسبت به نیاز و انتظاری که از مدل داریم، یک مدل مناسب رو دانلود کنیم.پلتفرم‌های زیادی برای دانلود مدل‌های زبانی (LLM) وجود دارن، حتی خود وب‌سایت Ollama هم بخشی رو برای دانلود مدل‌ها داره.https://ollama.com/libraryاما ما از Hugging Face استفاده می‌کنیم. چرا؟توی Hugging Face تنوع مدل‌ها بسیار بیشتره، از مدل‌های عمومی گرفته تا مدل‌هایی برای کاربرد‌های خاص (مثل مدل‌های تخصصی پزشکی، حقوقی و... )اکثر مدل‌هایی که داره، قابلیت سفارشی‌سازی (customize) دارن و میشه نسبت به نیازمندی‌ و منابعی که داریم بهینه‌شون کنیم.در کل مدل‌هایی که وب‌سایت Ollama ارائه میده، بیشتر مناسب کاربرایی هست که دنبال تجربه‌ ساده و سریع استفاده از مدل‌های آفلاین هستن، اما Hugging Face آزادی عمل و تنوع بیشتری داره و می‌تونیم نسبت به سخت‌افزار و نیازمندی، مدل مناسب‌تری رو دانلود کنیم.برای دانلود مدل از Hugging Face، اول وارد بخش Model ها بشین:https://huggingface.co/modelsهمونطور که مشخصه تو این بخش در حال حاضر 1,934,990 مدل وجود داره، ولی همونطور که صحبتش شد ما باید نسبت به کانفیگ سخت‌افزاری و نیازمون، مدل مناسب رو دانلود کنیم:Hugging Face / Modelsمدل‌های آفلاین عمومی قابل دسترس در زمینه تحلیل باینری و Malware Analysis فعلا تنوع زیادی ندارن و انتخاب بینشون خیلی سخت نیست، چون گزینه‌های اصلی‌ محدود میشه به Qwenمحصول علی‌بابا و Code LLaMAحالا ما برای ادامه کار، Qwen رو انتخاب می‌کنیم، چرا؟ 🤔مدل Qwen در زمینه تحلیل باینری، عملکرد بهتری از خودش نشون داده.از نظر مصرف منابع سخت‌افزاری بهینه‌تر عمل ‌می‌کنه.بنابراین Qwen رو جستجو می‌کنیم و نتایج رو بر اساس Recently updated مرتب می‌کنیم:تو این مرحله، انتخاب مدل در نهایت به منابع سخت‌افزاری‌ شما بستگی داره.مدل‌هایی که تو اسمشون عبارت‌های Instruct، Thinking یا Coder دارن، منابع سخت‌افزاری خیلی بالایی نیاز دارن و معمولا باید روی سرور یا Workstationهای با کانفیگ بالا ازشون استفاده کرد.مثلا مدل Qwen/Qwen3-Coder-480B-A35B-Instructحدود 480 میلیارد پارامتر داره و در حال حاضر کامل‌ترین مدل از این لیست به حساب میاد، ولی خب طبق مستندات نیاز به 64 هسته CPU، بیشتر از 512GB رم و کارت گرافیک NVIDIA A100 80GB داره. 😕 پس میریم سراغ مدل‌های خیلی خیلی پایین‌تر 😁از این لیست، مدل Qwen/Qwen3-14Bبا یک سیستم خونگی کانفیگ خوب هم قابل استفادست، این مدل به 64GB رم و یک کارت گرافیک RTX 4090 نیاز داره تا بتونه عملکرد خوبی داشته باشه.ما در نهایت کار رو با مدل Qwen/Qwen3-8Bادامه میدیم که 8 میلیارد پارامتر داره، این مدل رو میشه سر یه لپ‌تاپ معمولی با 16GB رم و یک کارت گرافیک RTX 3090 هم به راحتی استفاده کرد. (بدون نیاز به GPU و فقط با داشتن یک CPU خوب هم میشه عملکرد خوبی ازش گرفت.)سوالی که احتمالا پیش میاد اینه که:ما از یک مدل 480 میلیارد پارامتری رسیدیم به یک مدل ۸ میلیاردی! این مدل با این حجم از پارامتر اصلا می‌تونه نیاز ما رو جواب بده؟واقعیت اینه که طبیعتا هر چقدر کانفیگ سخت‌افزاری بالاتری داشته باشیم، می‌تونیم از مدل‌های قوی‌تری استفاده کنیم و خروجی بهتر و دقیق‌تری بگیریم، اما از اونجایی که هدف این پست آشنایی با روش کاره، همین مدل 8 میلیارد پارامتری در حدی که با روش کار آشنا بشیم نیازمون رو برطرف می‌کنه. ولی در نهایت، نیازه که شما بر اساس کانفیگ سخت‌افزاری‌تون مدل قوی‌تری را انتخاب کنید.⚠️ فقط یک نکته مهم: مدلی را انتخاب کنید که آخر اسمش gguf باشد. با وجود اینکه برای عملکرد بهتر مدل‌ها، GPU قطعا با اختلاف بهتره، ولی این دسته از مدل‌ها با بهینه‌سازی‌های GGUF و Quantization تا حد زیادی برای اجرا با CPU (بدون نیاز به GPU) بهینه شدن، مثلا شما می‌تونین بدون GPU و فقط با داشتن یک CPU خوب (حداقل 8 هسته) و 16GB رم، مدل‌های تا 14 میلیارد پارامتر رو هم خروجی خوبی بگیرین.خب پس نسبت به سخت‌افزاری که دارین، مدل مناسب رو انتخاب کنید و بعد وارد بخش Files and Versions اون مدل بشین: (در تصویر زیر با کادر قرمز مشخص شده)حالا اینجا Quantizationهای مختلفی ازش رو داریم که باید بر اساس مشخصه‌هایی که توی اسمشون هست اونی که مناسبه رو دانلود کنیم (تصویر زیر):Q4: (4 بیت) - کوچیک‌تر و سریع‌تر، ولی دقت کمترQ5: (5 بیت) - تعادل نسبتا مناسبQ6: (6 بیت) - نزدیک به دقت کامل اما حجم بیشترQ8: (8 بیت) - معمولا کمتر کوانتیزه‌ شده، همین باعث میشه حداکثر دقت و حجم بالاتر رو داشته باشه.K و M هم به ترتیب به نوع Quantization و روش بهینه سازی اشاره دارن.در واقع هر چقدر بیت‌ها کمتر باشن (مثلا Q4) مدل حجم کمتری داره و سریع‌تر اجرا میشه، ولی ازون طرف از دقت‌ش هم کم میشه. پس در نهایت ما مدل Qwen3-8B-Q4_K_M.gguf رو انتخاب می‌کنیم، چون فقط می‌خوایم با نحوه کار آشنا بشیم.این مدل دقت کمتری نسبت به بقیه‌شون داره، ولی حجم کمتر، سرعت بیشتر و منابع سخت‌افزاری کمتری رو نیاز داره.برای دانلود روی Use this model کلیک کنید و از بخشی که باز میشه Ollama رو انتخاب کنید، به این صورت:حالا از این قسمت، مدل دقیق مورد نظرمون رو انتخاب می‌کنیم تا بتونیم command مربوط به دانلودش رو copy کنیم:البته جای run رو با pull عوض می‌کنیم که فقط دانلودش کنه، فعلا نمی‌خوایم اجراش کنه، پس:ollama pull hf.co/Qwen/Qwen3-8B-GGUF:Q4_K_Mبعد از دانلود، برای اطمینان کامندollama list رو اجرا کنید، خروجی باید تموم مدل‌های نصب‌شده را لیست کنه.به این صورت:4- نصب OPEN UIحالا به یک UI نیاز داریم تا بتونیم با مدلی که دانلود کردیم ارتباط بگیریم، البته استفاده از UI اجباری نیست و میشه به صورت CLI هم با مدل کار کرد، ولی خب اینجوری کار کردن باهاش خیلی‌ ساده‌تر و خروجی هم قشنگ‌تر نشون داده میشه. ابزارهای رایگان زیادی توی این زمینه وجود دارن، که Open UI یکی از بهتریناشونه، چون هم سبکه و هم رابط کاربری ساده‌ای داره.این ابزار را می‌تونید از GitHub نصب و اجرا کنید:https://github.com/open-webui/open-webuiنصب و اجرا کردنش خیلی ساده‌ست، خودشم تو مستنداتش خیلی خوبو قشنگ توضیح داده، سر همین اینجا دیگه وارد جزئیات نصب و اجرا نمی‌شیم.در نهایت، بعد از run کردنش، بر اساس Portی که مشخص کردین یک URL بهتون میده، مثلا:http://localhost:3000با باز کردن این آدرس، اگه همه چیز به‌ درستی انجام شده باشه باید برسیم به این صفحه:البته اولین باری که اجراش می‌کنید، ازتون می‌خواد که یک اکانت کاربری (Local) ایجاد کنید.5- نصب Ghidra MCPتا اینجای کار ما Ghidra، یک LLM آفلاین، یک Engine برای اجرای مدل و یک UI برای ارتباط با مدل رو نصب کردیم. حالا باید ارتباط بین Ghidra و این مدل را ایجاد کنیم. برای این کار، لازمه اکستنشن Ghidra MCP را دانلود و نصب کنیم.ریپازیتوری اصلی این اکستنشن:https://github.com/LaurieWired/GhidraMCPاما ما از نسخه Fork شدش استفاده می‌کنیم:https://github.com/starsong-consulting/GhydraMCPنسخه Fork شده برای هر نمونه Ghidra که اجرا می‌کنیم، یک Port جدا اختصاص میده و اینجوری میشه برای هر Ghidra ای که باز داریم، بدون تداخل با مدل ارتباط داشته باشیم.اگه معمولا بیشتر از یک Ghidra به صورت همزمان اجرا نمی‌کنید، نیازی نیست حتما نسخه Fork رو نصب کنید.برای استفاده از این اکستنشن، از بخش release، فایلی که تو اسمش complete داره رو دانلود می‌کنیم:starsong-consulting/GhydraMCPبعد از دانلود از حالت فشرده خارجش می‌کنیم و می‌رسیم به این 4 فایل:حالا وارد Ghidra می‌شیم و از منوی File، گزینه Install Extensions رو انتخاب می‌کنیم:تو این مرحله از بخشی که باز میشه، آیکن + رو انتخاب کنین و از بین 4 فایل مرحله قبل، فایل zip رو بهش بدین، احتمالا یه پیام بیاد که نسخه Ghidra با نسخه Extension هماهنگ نیست، این موضوع مهم نیست و شما Install anyway رو بزنین.در نهایت Ghidra MCP باید به این صورت به لیست Extensionها اضافه شده باشه:حالا یک بار Ghidra رو restart کنید و برای اطمینان از نصب صحیح Extension، از منوی File بخش Configure و بعد بخش Configure از زیر مجموعه‌ی Developer رو انتخاب کنید:اینجا باید GhydraMCPPlugin توی لیست باشه و تیک‌ش هم خورده باشه (اگه تیک‌ش نخورده بود، خودتون بزنین):6- ارتباط LLM و Open UI با Ghidra MCPخب حالا نیاز داریم ارتباط بین Ghidra MCP با LLM رو ایجاد کنیم. این کار با استفاده از فایل bridge_mcp_ghidra.pyانجام میشه (یکی از همون 4 فایلی که مرحله قبل دانلود کردیم)، این فایل همونطور که از اسمش هم مشخصه مثل یک پُل عمل می‌کنه و ارتباط بین Ghidra MCP رو با LLM فراهم می‌کنه. حالا ما این وسط یه Open UI هم گذاشتیم، پس میشه به این صورت:برای اجرا کردن bridge_mcp_hydra.pyمی‌تونیم خیلی ساده با python اجراش کنیم، ولی اینجوری باید تعداد خیلی زیادی dependency رو یکی یکی با pipدانلود کنیم، روش ساده‌تر و سریع‌تر اینه که بیایم از uv استفاده کنیم. همونطور که می‌دونید uv یک Package Manager پایتونه که با Rust نوشته شده، نسبت به pip سرعت بالاتری داره، مدیریت dependencyها رو خیلی ساده‌تر کرده و هر چی نیازه رو خودش دانلود می‌کنه.این ابزار رو می‌تونید از Githubش دانلود کنید:https://github.com/astral-sh/uvبعد از نصب uv، حالا باید bridge_mcp_hydra.pyرو به این صورت اجرا کنیم:uv run bridge_mcp_hydra.pyالان تا اینجای کار همه ارتباط‌ها برقرار شد به جز ارتباط Open UI که هنوزم هیچ ارتباطی با Ghidra نداره. در واقع ما الان اومدیم با استفاده از bridge_mcp_hydra.py ارتباط بین Ghidra و مدل رو برقرار کردیم، حالا باید به نوعی Open UI هم وارد این زنجیره ارتباطی بشه.برای اینکه بتونیم این ارتباط رو ایجاد کنیم، Open UI از ما یک آدرس OpenAPIمی‌خواد. در واقع اینجا bridge_mcp_hydra.py که نقش یک پُل بین Ghidra با بیرون رو بر عهده داره، باید یک OpenAPI بده بهمون.سریع‌ترین راه حل چیه؟ استفاده از mcpo :https://github.com/open-webui/mcpomcpo این امکان رو فراهم می‌کنه که بشه هر ابزار MCP رو به‌ عنوان یک HTTP Server که از OpenAPI پشتیبانی می‌کنه به صورت RESTful به بیرون Expose کرد.برای نصب mcpo به این صورت عمل می‌کنیم:pip3 install mcpoو حالا به جای اینکه bridge_mcp_hydra.py رو مستقیم اجرا کنیم، این کارو از طریق mcpo انجام میدیم:uvx mcpo --uv run bridge_mcp_hydra.pyاینجوری در کنار اینکه bridge_mcp_hydra.py اجرا شده، یک آدرس OpenAPI هم بهمون میده:حالا فقط کافیه این آدرس رو به Open UI بدیم، از این مسیر:Settings → Tools → Add Connectionحالا اگه به صفحه اصلی Open UI برگردید، یک آیکن شبیه آچار اضافه شده که با کلیک روی اون، به لیست ابزارهای فعال Ghidra MCP هم اضافه شده:الان همه‌چیز آمادست 😍برای کار باهاش، کافیه فایل مورد نظر رو توی Ghidra باز کنید و بعد از طریق Open UI هر سوال یا درخواستی دارید به‌صورت ChatBotی مطرح کنید تا مدل براتون انجام بده.بعضی از کارهایی که می‌تونید ازش بخواید (بر اساس مستندات Ghidra MCP):دیکامپایل خودکار توابعگرفتن کد سطح بالا (C-style) از توابع باینری و دادنش به مدل برای تحلیل یا بازنویسیدیس‌اسمبلی دقیق توابعدریافت دستورهای اسمبلی به‌صورت کامل و ساختارمند برای بررسی‌های سطح پایینتحلیل جریان داده (Data Flow Analysis)مدل می‌تونه از مسیر داده‌ها در کد مطلع بشه و بخش‌های مشکوک یا مهم رو شناسایی کنهتولید و نمایش گراف فراخوانی توابع (Call Graph)ساختار ارتباط توابع رو به مدل بده تا رفتار کلی برنامه رو تحلیل کنهکشف و مدیریت اینستنس‌های Ghidra در حال اجراپیدا کردن Ghidraهای باز، اتصال و جابجایی سریع بین اون‌‌ها برای کار روی چند پروژه هم‌زمانخواندن مستقیم حافظه برنامهدسترسی به بایت‌های خام برای بررسی داده‌ها یا ساختارهای خاصدریافت لیست تمام رشته‌ها (Strings)شناسایی پیام‌ها، کلیدها یا متن‌های مخفی در باینریاضافه کردن یا تغییر کامنت در کدمدل می‌تونه توضیحات تحلیلی یا یادداشت‌های خودش رو مستقیما در Ghidra ذخیره کنهالبته همونطور که اول پست گفتم، هنوز AI در حوزه تحلیل باینری به اون نقطه ایده‌آل نرسیده (حداقل در مدل‌های عمومی)، اما همین الان هم می‌تونه سرعت کار رو تا حد زیادی بالا ببره. امیدوارم روز به‌ روز چیزای بیشتری رو بتونیم به AI بسپاریم تا فرصت بشه وقت‌مون رو روی حوزه‌های بیشتر و جدیدتری بذاریم. 🤠امیدوارم این پست براتون مفید بوده باشه.اگه سوالی داشتید می‌تونیم لینکدین در ارتباط باشیم.شاد و موفق باشید... ❤️</description>
                <category>عماد عابدینی</category>
                <author>عماد عابدینی</author>
                <pubDate>Fri, 15 Aug 2025 00:23:22 +0330</pubDate>
            </item>
                    <item>
                <title>چالش‌های Pentest اپلیکیشن‌های اندرویدی (بخش دوم)</title>
                <link>https://virgool.io/@emad_abedini/%DA%86%D8%A7%D9%84%D8%B4-%D9%87%D8%A7%DB%8C-pentest-%D8%A7%D9%BE%D9%84%DB%8C%DA%A9%DB%8C%D8%B4%D9%86-%D9%87%D8%A7%DB%8C-%D8%A7%D9%86%D8%AF%D8%B1%D9%88%DB%8C%D8%AF%DB%8C-%D8%A8%D8%AE%D8%B4-%D8%AF%D9%88%D9%85-cbbxqwmradnb</link>
                <description>در بخش قبل با مفاهیم پایه‌ Hooking در اپلیکیشن‌های اندرویدی آشنا شدیم. قبل از شروع این بخش، اول مهم‌ترین نکات بخش قبل رو مرور کنیم:بیشتر اپلیکیشن‌های اندرویدی (شاید چیزی حدود 80%) برای پیاده‌سازی مکانیزم‌های امنیتی از کتابخونه‌ و اسکریپت‌های عمومی استفاده کردن، بنابراین خیلی راحت و بدون هیچ چالشی میشه الگوریتم‌های امنیتی‌شون رو Bypass کرد.برای انجام اصولی فرایند Pentest و Reverse اپلیکیشن‌های اندرویدی، در کنار دانش برنامه‌نویسی Native، نیازه که با اسمبلی هم آشنا باشیم. (دانش برنامه‌نویسی بیشتر = تحلیل و Reverse دقیق‌تر)از مهم‌ترین روش‌هایی که برای Bypass مکانیزم‌های امنیتی استفاده میشه، Patch و Hook کردن اون بخش از Codeه که باعث ایجاد محدودیت شده.اضافه کردن مکانیزم‌های امنیتی به اپلیکیشنی که ساختار و معماری امنی نداره بی فایدست. در واقع الگوریتم‌های امنیتی زمانی می‌تونن به درستی عمل کنن که در ساختار درستی پیاده‌سازی شده باشن.هوک کردن همیشه ساده نیست و نسبت به نحوه پیاده‌سازی مکانیزم‌های امنیتی ممکنه با چالش‌هایی همراه باشه.در فرایند Pentest و Reverse اپ‌های اندرویدی، ممکنه با چالش‌های زیادی روبه‌رو بشیم که فقط بخشی از این چالش‌ها به Hook کردن مربوط میشه.یکی از مشکلات بخش قبلی طولانی بودنش بود، حدود 6200 کلمه (تقریبا چیزی حدود 25 صفحه A4) که برای یک پست واقعاً زیاده. دلیل اصلی این طولانی شدن هم این بود که بخش زیادی به مباحث پایه و مقدماتی اختصاص داده شد.همونطور که توی پست قبلی توضیح دادم در این رشته پست‌ها فرض شده که شما با اصول اولیه Reverse اپلیکیشن‌های اندرویدی آشنا هستین (پست قبلی رعایت نکردم این موضوعو 😁)حالا برنامه اینه که از این بخش به بعد، مستقیم بریم سر اصل مطلب که تا جای ممکن هر بخش کوتاه و خلاصه باشه. اگه با مباحث پایه آشنایی ندارید نگران نباشید اصلا، محتوای خیلی زیادی (حتی فارسی) در این زمینه وجود داره که خیلی راحت می‌تونین با یه سرچ ساده یادشون بگیرین.و اما عنوان این بخش:وقتی که سورس‌کد خودش رو قایم کرده! 😕خیلی وقت‌ها اپلیکیشن رو دیکامپایل می‌کنیم اما خبری از سورس‌کد نیست! تو این حالت معمولا سورس‌کد Encrypt ،Compress و Obfuscate شده و در زمان اجرا (Runtime)، رمزگشایی و فراخوانی میشه، اصطلاحا میگن سورس‌کد Pack شده.هدف از پیاده‌سازی این تکنیک مخفی کردن کد در تحلیل استاتیک و در نتیجه پیچیده‌تر کردن فرایند Reverse Engineering هست.حالا سوال اینه که کد کجاست؟ 🤔 معمولا با یکی از این دو حالت روبرو هستیم:دانلود در لحظه اجرا (Runtime): تو این حالت کد از اینترنت و در لحظه اجرا بارگذاری میشه (این روش بیشتر توی malwareها دیده میشه).جاسازی و رمزنگاری در فایل‌های اپلیکیشن: تو این حالت کد داخل فایل‌های اپلیکیشن وجود داره و در لحظه اجرا Decrypt و فراخوانی میشه. تو این روش، بسته به الگوریتم مورد استفاده، اون بخش از کد که قراره در زمان اجرا فراخوانی بشه، به صورت Encrypt شده ممکنه یه فایل dex باشه، ممکنه داخل فولدر Assets باشه، ممکنه یه فایل باینری باشه، ممکنه توی Resources باشه، شاید تو فولدر Raw باشه و حتی ممکنه به چند بخش تقسیم شده باشه و هر بخش در یک فایل مجزا قرار گرفته باشه.بعضی وقتا هم ممکنه در نگاه اول سورس‌کد وجود داشته باشه و همه چیز عادی به نظر برسه ما هم با خوشحالی بشینیم کد رو تحلیل کنیم، اما در واقعیت بخش اصلی کد قراره در لحظه اجرا به سورس‌کد اضافه یا جایگزین بشه.معمولا RATهای درست حسابی Shell Code اشون رو اینجوری اضافه می‌کنن.بنابراین نسبت به الگوریتم Packer، تحلیل و دسترسی به سورس‌کد به‌صورت استاتیک، یا امکان‌پذیر نیست یا بسیار زمان‌بره.اگه بخوایم دقیق‌تر بهش نگاه کنیم:پیاده‌سازی این مکانیزم به سه روش امکان‌پذیره:توسعه اسکریپت اختصاصیاستفاده از اسکریپت‌ها و کتابخانه‌های عمومیاستفاده از راهکارهای تجاری مثل DexProtector، GuardSquare و موارد مشابهاسکریپت‌ها، کتابخونه‌های عمومی و حتی راهکارهای تجاری‌ای که برای پیاده‌سازی این مکانیزم استفاده میشه خیلی‌هاشون Signature اشون درومده و همین باعث شده اسکریپت‌های Decrypt اشون هم در دسترس باشه. از طرفی توسعه اسکریپت اختصاصی‌ به دانش زیادی در زمینه‌های Android Internals، تکنیک‌های Reverse Engineering، الگوریتم‌های Encryption و Cryptography نیاز داره که خب این باعث میشه پیاده‌سازی یک اسکریپت اختصاصی امن، زمان‌بر و پیچیده باشه.پس انتخاب روش مناسب به سطح امنیت مورد نیاز اپلیکیشن بستگی داره:- اگه هدف صرفا افزایش نسبی امنیت باشه، می‌تونیم از کتابخونه‌های عمومی استفاده کنیم که حداقل باعث شه تا حدودی فرایند Reverse زمان بیشتری بگیره.- اما در موارد حساس‌ مثل اپلیکیشن‌های مالی و بانکی، باید از ترکیب راهکارهای تجاری و اسکریپت اختصاصی استفاده کرد. (اکثر اپلیکیشن‌های پرداخت بین‌المللی برای پیاده‌سازی این تکنیک، از ترکیب راهکارهای تجاری و الگوریتم‌های اختصاصی استفاده کردن)سوالی که ممکنه براتون پیش بیاد اینه که:پس الان اکثر اپلیکیشن‌‌های حساس (بانکی، پرداخت و از این قبیل) حداقل با استفاده از الگوریتم‌های عمومی این مکانیزم رو پیاده‌سازی کردن؟نه! 😐 طبق بررسی‌هایی که انجام دادم شاید بشه گفت کمتر از 20% اپلیکیشن‌های حساس داخلی این تکنیک رو پیاده‌سازی کردن. (البته که قطعا من همه اپلیکیشن‌ها رو تست نکردم، ولی در زمینه اپلیکیشن‌های بانکی و پرداخت فقط توی 3 اپلیکیشن به صورت صحیح پیاده‌سازی شده بود)تعداد زیادی از بانک‌های داخلی Core Banking و اپلیکیشن‌اشون مشترکه (رنگ و آیکنش عوض شده)، در واقع اگه به فرض 25 نمونه اپلیکیشن بانکی داشته باشیم، در واقع چیزی حدود 14 نمونه برای بررسی داریم.راستی اینم در نظر داشته باشین:زمانی که سورس‌‍‌کد رو Pack می‌کنیم، نسبت به الگوریتمی که پیاده‌سازی کردیم معمولا حجم خروجی زیاد میشه (نه خیلی).احتمال داره Google Play اپلیکیشن رو Reject کنه چون قبل از تایید انتشار، سورس‌کد بررسی میشه. (متاسفانه کافه بازار ظاهرا فقط بر اساس نتیجه Multi AV انتشار یا عدم انتشار اپلیکیشن‌ها رو بررسی می‌کنه!)نسبت به الگوریتمی که پیاده‌سازی کردیم، Decrypt سورس‌کد ممکنه به دلیل ضعیف بودن منابع روی دیوایس‌های خیلی قدیمی با مشکل روبرو بشه.اگه از راهکارهای تجاری استفاده کنین که خب نگرانی‌ای بابت موضوعات بالا نیست، ولی اگه خواستین الگوریتم اختصاصی خودتون رو بنویسین یا از کتابخونه‌های عمومی استفاده کنین به این موضوعات هم دقت داشته باشین، در واقع باید Decryption و Compression در سطح مناسبی باشه (نه خیلی شدید و نه خیلی ضعیف)خب بگذریم، برگردیم سر ادامه موضوع...چجوری می‌تونیم به سورس‌کد برسیم؟اول باید بررسی کنیم اپلیکیشن از کتابخونه‌ و روش‌های عمومی استفاده کرده یا الگوریتم اختصاصی. برای این کار می‌تونیم از ابزارهایی مثل APKiD استفاده کنیم، یا به صورت دستی (بدون ابزار) نوع Packer رو تشخیص بدیم و برای استفاده‌های بعدی Yara Ruleش کنیم.حالا اگه از کتابخونه‌ و روش‌های عمومی استفاده شده باشه و اسکریپت Decryptش هم موجود باشه که خیلی راحت به سورس‌کد می‌رسیم و چالشی هم نیست. پس میایم فرض می‌کنیم از روش اختصاصی استفاده شده یا به هر دلیل دسترسی به اسکریپت Decrypt نداریم.روش اول: درصد بالایی از Packerها نحوه کارشون به این صورته که بخش Encrypt و Compress شده در زمان اجرا (Runtime)، رمزگشایی و با روش ‌هایی مثل استفاده از کلاس DexClassLoader در حافظه Load میشه و بعد فایل unpack شده فورا از روی دیسک حذف میشه. تو این حالت می‌تونیم فانکشن های مرتبط با حذف فایل رو hook کنیم که Remove شدن اتفاق نیفته و بعد میریم فایل unpack شده رو از روی دیسک برمی‎‌داریم 😍روش دوم: حالت دوم زمانی استفاده میشه که روش اول جواب نمیده. بعضی Packerها ممکنه به‌جای استفاده از توابع رایج، از روش‌هایی استفاده کنن که ما به هر دلیل نتونیم جلوی حذف شدن فایل از دیسک رو بگیریم. یه مورد که تازگیا دیدم این بود که چندین تابع مرتبط با remove تعریف کرده بود و شرط گذاشته بود که اگه تعدادی مشخص از این توابع به طور کامل اجرا شدن (فایلی مشخص رو حذف کردن) بعد میرفت سراغ Decrypt بخش Pack شده، همین باعث میشد روش اول جواب نده چون ما معمولا میایم تموم توابع مرتبط با remove رو هوک می‌کنیم (البته که تو این حالت هم میشه با تحلیل استاتیک کد به همون روش اول به نتیجه رسید ولی زمان‌بر میشه). در روش دوم، ما میایم به جای اینکه جلوی حذف فایل unpack شده رو بگیریم، از Memory Mapping برای Pull کردنش استفاده می‌کنیم.روش دوم معمولا همیشه جواب میده، مگه اینکه اون بخش از Memory که فایل Pack شده اونجا قرار گرفته Encrypt شده باشه. تو این حالت نیاز به Tracing و Debuging داریم.نحوه انجام روش اول: (جلوگیری از حذف فایل unpack شده)همونطور که توضیح دادم باید یک اسکریپت بنویسیم که جلوی حذف فایل رو بگیره. اینجا بستگی داره حذف فایل با استفاده از Java Methods انجام شده باشه یا Native Methods.توی جاوا یکسری Method داریم که به طور مشخص برای حذف فایل استفاده میشن، مثل delete و deleteIfExists از کلاس‌های java.io.File و java.nio.file.Files ، ولی یکسری Method هم داریم که کاربرد اصلی‌شون حذف فایل نیست ولی به نوعی میشه باهاشون فایل رو غیر قابل دسترس کرد، مثلا با java.nio.file.attribute.FileAttributeView میشه با دستکاری مجوزهای فایل بدون اینکه فایل حذف بشه به نوعی غیرقابل استفادش کرد. ولی ما خیلی پیچیدش نمی‌کنیم، میایم فقط همون delete و deleteIfExists رو در نظر می‌گیریم (فوقش اینه که روش اول جواب نمیده، میریم روش دوم)اسکریپت‌ها رو می‌تونین از اینجا داشته باشین:Android-Pentest-Challengesدر اسکریپت زیر، متد delete از کلاس java.io.File رو هوک کردیم. در خط 5، از this که میشه همون شیء ایجاد شده از File، مسیر فایل (path) که قراره حذف بشه رو گرفتیم. در خط ۹ مقدار true رو برگردوندیم تا متد اصلی delete اجرا نشه.حالا چرا true رو برگردوندیم؟ چون وقتی متد delete مقدار true رو برمی‌گردونه، یعنی فایل با موفقیت حذف شده. اگه فایل به هر دلیلی حذف نشه (مثلاً فایل توی اون مسیر وجود نداشته باشه) مقدار false برمی‌گرده، اما اینجا عمدا مقدار true برگشت داده شده که فکر کنه فایل حذف شده، در حالی که واقعا حذف نشده.قبل از اینکه خروجی بالا رو ببینم، اگه پیاده‌سازی الگوریتم Packing در Native Methods باشه باید چکار کرد؟ روش کار دقیقا همونه، باید بیایم فانکشن‌های مرتبط با حذف فایل که در ++C/C استفاده میشن رو هوک کنیم، فانکشن‌هایی مثل:remove, unlink, rmdirمثلاً اینجا فانکشن remove رو هوک کردیم:مقدار 0 رو return کردیم، چون وقتی این فانکشن فایل رو با موفقیت حذف کنه، مقدار 0 رو برمی‌گردونه. در خط 9، نوع داده رو int گذاشتیم چون مقدار برگشتی از نوع int هست و از pointer استفاده کردیم که به NativeCallback بگیم آرگومان ورودی رو باید به عنوان pointer مدیریت کنی. (البته اگه مستندات Frida رو در کنار مستندات فانکشنی که می‌خواین هوک کنید مطالعه کنید، تمام موارد به‌طور کامل توضیح داده شده.)در نهایت، اسکریپت رو اجرا می‌کنیم و خروجی به صورت زیر میشه:الان خیلی راحت می‌تونیم فایل unpack شده رو از مسیر زیر pull کنیم، بعد unzip ش کنیم و به سورس‌کد برسیم:/data/user/0/ir.emad-abedini.app.pico/code_cache/secondary-dexes/base.apk.classes1.zipنحوه انجام روش دوم: (Memory Mapping)خب همونطور که توضیح دادم ممکنه حالتی پیش بیاد که به هر دلیل روش اول جواب نده، تو این حالت می‌تونیم از Memory Mapping استفاده کنیم. در واقع فایل Pack شده اول روی دیسک ایجاد میشه، بعد روی حافظه بارگذاری میشه و بعد از روی دیسک فورا حذف میشه. (اما همچنان روی حافظه هست و میشه مستقیم از روی حافظه dumpش کرد)پس اول Memory Mapping رو به این صورت انجام میدیم: (طبیعتا اپلیکیشن باید قبلش اجرا باشه)[ADB_SHELL] : /proc/[pid]/mapsتوی خروجی با یکم اسکرول کردن میشه راحت فایل unpack شده رو پیدا کرد. اگه پیدا نکردین (که البته بعیده)، معمولا اون بازه‌ای که طولش بیشتره میشه همون فایل unpack شده، الان اینجا طول بین 81e5026000 و 81e500d000 میشه 102,400 بایت (اگه دقت کنید بقیه region ها طول کمتری دارن)الان رسیدیم به آدرسی از دیسک که فایل از اون آدرس Extract شده و روی Memory بارگذاری شده. حالا همونطور که توضیح دادم درسته به مسیر فایل رسیدیم، ولی نمی‌تونیم pullش کنیم، چون فایل بعد از بارگذاری روی Memory، سریع از روی دیسک حذف شده (فایل روی دیسک نیست که بشه pullش کرد)، حالا چکار کنیم؟میایم اون Region از حافظه رو dump می‌گیریم و خروجی رو در یک فایل ذخیره ‌می‌کنیم:تنها نکته اینجاست آدرسی که قراره خروجی dump ذخیره شه باید دایرکتوری خود اپلیکیشن باشه، یعنی:/data/data/&lt;package_name&gt;نکته بعدی که وجود داره ASLRه، در نسخه‌های قبل از 4 اندروید زمانی که اپلیکیشن اجرا میشد همه بخش‌ها (کد، کتابخونه‌ها، Stack و Heap) همیشه توی آدرس مشخصی از Memory قرار میگرفتن که این ثابت بودن مشکلات امنیتی زیادی مثل buffer overflow و Return-oriented programming رو به وجود میاورد، از اندروید 4 به بعد ASLR اضافه شد که هر بار اپلیکیشن اجرا میشه این آدرس‌ها تغییر کنه. پس اگه بیایم اسکریپتی که نوشتیم رو Spawn کنیم، اپلیکیشن دوباره اجرا میشه و زمانی هم که بیاد بالا فایل unpack شده بر اساس ASLR جای دیگه‌ای از حافظه قرار گرفته و اینجوری اسکریپت ما کار نمی‌کنه.پس جای Spawn اسکریپت رو به این صورت Attach می‌کنیم:frida -U [APP_NAME] -l [SCRIPT]که در نهایت نتیجه به این صورت میشه:الان راحت میتونیم فایل unpack شده رو از این مسیر pull کنیم.خب رسیدم به پایان بخش دوم 😍 (از احتمالا 11 ام)نکته خاص نگفته‌ای به نظرم نمیرسه، فقط اینکه:برای رسیدن به فایل unapack شده، یکسری ابزار آماده هم وجود داره، مثلا Objection هم این امکان رو داره. درحال حاضر ابزارهایی که برای Bypass این مکانیزیم (Packing) منتشر شدن محدودیت‌های خیلی زیادی دارن (فقط روی الگوریتم‌های ساده جواب میدن)، ولی خب برای صرفه‌جویی در زمان، پیشنهادم اینه که بهتره اول از ابزارهای آماده استفاده کنیم و اگه جواب نگرفتیم بعدش بیایم سراغ اسکریپت نوشتن.یکی دیگه از روش‌های رسیدن به فایل unpack شده (در صورتی که فایلی که قراره Decrypt شه DEX یا Jar باشه) هوک کردن متدهایی مثل ClassLoader و PathClassLoader هست.دسته‌ خیلی کمی از اپلیکیشن‌ها Memory رو Encrypt می‌کنن، تو این حالت dump گرفتن از Memory عملا هیچ کاربردی نداره. روش کار به این صورته که تموم stringهایی که داره رو بگیرین، فانکشن و متد‌ها رو جدا کنین و بر اساس فانکشن یا متدی که مرتبط با مدیریت حافظه‌ست debug رو شروع کنین ( به نتیجه میرسین)امیدوارم که این پست براتون مفید بوده باشه...اگه سوالی داشتید می‌تونیم لینکدین در ارتباط باشیم.شاد و موفق باشید... ❤️ویرایش: (1404/05/17)با توجه به اینکه این پست و پست قبلی بازخورد زیادی نداشتن، فعلا این موضوع رو تا همین‌جا نگه می‌داریم. برنامه‌م اینه که از پست‌های بعدی سراغ موضوعات دیگه (در همین حوزه امنیت) برم.برای دنبال‌کردن مطالب بعدی خوشحال می‌شم در لینکدین در ارتباط باشیم ❤️</description>
                <category>عماد عابدینی</category>
                <author>عماد عابدینی</author>
                <pubDate>Thu, 09 Jan 2025 18:39:37 +0330</pubDate>
            </item>
                    <item>
                <title>چالش‌های Pentest اپلیکیشن‌های اندرویدی (بخش اول)</title>
                <link>https://virgool.io/@emad_abedini/%DA%86%D8%A7%D9%84%D8%B4-%D9%87%D8%A7%DB%8C-pentest-%D8%A7%D9%BE%D9%84%DB%8C%DA%A9%DB%8C%D8%B4%D9%86-%D9%87%D8%A7%DB%8C-%D8%A7%D9%86%D8%AF%D8%B1%D9%88%DB%8C%D8%AF%DB%8C-%D8%A8%D8%AE%D8%B4-%D8%A7%D9%88%D9%84-ekiervbb3aik</link>
                <description>توی این پست قراره در خصوص نحوه کرک اپلیکیشن‌های اندرویدی، چالش‌های Bypass مکانیزم‌های امنیتی و در کل هر چیزی که به Pentest و Reverse engineering اپ‌های اندرویدی مربوط میشه صحبت کنم.حدود 3 سال پیش یه دوره ویدیویی 10 قسمته با موضوع &quot;کرک اپلیکیشن‌های اندرویدی&quot; گذاشته بودم یوتیوب، آپارات و کانال تلگرام، که البته اونقدری که انتظارشو داشتم دیده نشد. (الان که چک کردم تا این لحظه در مجموع 3259 بار دیده شده) https://virgool.io/@emad_abedini/%D8%AF%D9%88%D8%B1%D9%87-%DA%A9%D8%B1%DA%A9-%D8%A7%D9%BE%D9%84%DB%8C%DA%A9%DB%8C%D8%B4%D9%86-%D9%88-%D8%A8%D8%A7%D8%B2%DB%8C-%D9%87%D8%A7%DB%8C-%D8%A7%D9%86%D8%AF%D8%B1%D9%88%DB%8C%D8%AF%DB%8C-%D8%A2%D9%BE%D8%A7%D8%B1%D8%A7%D8%AA-ygs0cnxmvcpz در کل بازخورد خوبی از اون دوره گرفتم (هر کس دیده راضی بوده 😁)، سوالاتی هم که پرسیده میشد معمولا یکی از اینا بود:فلان بازی رو چجوری هک کنم؟ 😒چقدر میگیری به اکانتم سکه اضافه کنی؟ 😐😒یه دسته سومی هم بودن که از سوالاشون مشخص بود دوره رو با هدف یادگیری مفاهیم دیدن، سوالاشون هم به هدف یادگیری بیشتر بود. (نه کرک بازی 😶) 😍طبیعتا دلیل این پست که البته قراره حدود 11-10 پست بشه، دسته سوم میشن و در کنارش دلایل زیر:در خصوص موضوعاتی مثل Pentest و در کل امنیت اپ‌های موبایلی، محتوای خیلی کمتری نسبت به حداقل امنیت Web منتشر شده! دوره‌های (مخصوصا فارسی) که به اسم پیشرفته در این زمینه منتشر شده نهایتا تا سطح متوسط رو پوشش دادن.اوضاع امنیت اپ‌های موبایلی داخلی خوب نیست! نمونه‌ش این که حدود 80% از اپ‌های بانکی و مرتبط با پرداخت داخلی اکثر مکانیزم‌های امنیتی‌ای که دارن با اسکریپت‌های Public خیلی راحت Bypass میشه! تعداد خیلی زیادی از تیم‌های Pentest اندروید (شاید بشه گفت چیزی حدود 70% اشون)، برای Bypass الگوریتم‌های امنیتی یا میان کلا از اسکریپت‌های آماده استفاده می‌کنن یا نهایت یکم تغییرش میدن و اگه جواب نده اون بخش رو به عنوان بدون آسیب‌پذیری گزارش میدن. (که البته همونطور که گفتم وقتی حتی خیلی از اپ‌های بانکیمون با همین اسکریپت‌های آماده Bypass میشن، واقعیت اینجاست که خیلی وقتا نیازی هم به یادگیری عمیق‌تر احساس نمیشه) دلیل اینکه اپ‌های بانکی و پرداخت رو زیاد مثال می‌زنم، چون بالاترین امنیت رو باید این دسته از اپ‎‌ها داشته باشن.و مورد آخر اینکه، یه تعداد زیادی از اپلیکیشن‌های داخلی (خصوصا سازمان‌های دولتی) مکانیزم‌های امنیتی زیادی به کدشون اضافه کردن به این امید که امنیت رو بردن بالا، اما در عمل تأثیر چندانی نداشته! پراید رو هر کاریش کنین بازم امنیت نداره، حالا ما بیایم ایربگ بذاریم براش یا سپرش رو تقویت کنیم تا چه حد میتونه کمک کنه وقتی پراید کلا ساختار امنی نداره!؟داستان دقیقا همینجاست، الگوریتم‌های امنیتی زمانی می‌تونن به درستی عمل کنن که توی ساختار درستی استفاده بشن. مورد‌های زیادی پیش اومده که بعد از Pentest بهشون گفته شده باید ساختار و معماری پروژه رو کامل عوض کنین ولی به دلیل هزینه‌ای که توسعه دوباره برای اون سازمان داشته هنوزم که هنوزه روی همون ساختار قبلی‌ان و اومدن بازم الگوریتم‌های امنیتی رو بیشتر کردن! 😐اگه برنامه‌نویس اندروید هستین و یا در زمینه امنیت فعالیت دارین، این رشته پست‌ها احتمالا براتون جالب باشه. هدف از این رشته پست‌ها اینه که تا حدودی اوضاع مواردی که بالا اشاره کردم رو بهتر کنه...پیش‌نیاز ها:توی این رشته پست‌ها (که این میشه پست اول)، فرض شده که شما با مقدمات برنامه‌نویسی و امنیت اندروید آشنا هستین، البته من بازم تا جایی که پست خیلی طولانی نشه توضیح میدم موضوعات رو، ولی در کل پیش‌نیازهایی که لازم داریم ایناست:با برنامه نویسی اندروید آشنا باشین، البته منظورم برنامه نویسی به صورت Nativeه، نه Cross-Platform مثل فلاتر، React Native و از این قبیل. (نه که Cross Platformی ها خوب نباشن، در جای خودشون خیلی خیلی هم خوبن، سرعت توسعه به شدت بالا میره، بعد از Compile کلی از مکانیزم‌های امنیتی به صورت پیشفرض رعایت شده و کلی نکته خوب دیگه دارن، ولی زمانی که صحبت از Pentest و Reverse اپ‌های اندرویدی میشه شما باید با ساختار اصلی اپ‌های اندرویدی آشنا باشین و آشنایی با ساختار اصلی اپ‌های یک OS زمانی به دست میاد که برای اون OS به صورت Native (مثلا برای اندروید با Java و Kotlin) توسعه بدین. مورد بعد آشنایی با مقدمات Hook کردن توی اندرویده که معمولا با Frida انجام میشه. (اگه با Frida و مفهوم Hook آشنا نیستین نگران نباشین، لینک چند تا آموزش کوتاه و خلاصه شده رو در ادامه براتون گذاشتم)مورد بعد آشنایی کلی با Assembly هست. بازم نگران نباشین فقط اسمش ترسناکه. لازم نیست اسمبلی رو از اول شروع کنین به خوندن، برای شروع اگه حداکثر 20% رو هم یاد بگیریم کافیه، کلی هم Cheat sheet براش هست که تازه بازم لازم نیست چیزی رو حفظ کنین، یه چند تا اپلیکیشن رو Decompile کنین و کنارش Cheet Sheet و یه AI chatbot (در حال حاضر پیشنهادم ChatGPT و Claudeه) داشته باشین بعده یه مدت کوتاه می‌بینین اسمبلی اونقدرا هم که فکر می‌کنیم پیچیده نیست و میشه باهاش کنار اومد.این نکته هم در نظر بگیرین که در حال حاضر معماری اکثر دیوایس‌های اندروید و IOSی ARM هست که یکمی syntax متفاوتی با x86 داره (ARM راحت‌تره)، ولی اگه از روی علاقه شخصی تصمیم به یادگیری Assembly گرفتین، پیشنهادم اینه که بر اساس X86 برین جلو چون باعث میشه درک کامل‌تری از پردازنده‎‌ها داشته باشین و بعد ARM براتون در حد بازی به حساب میاد.بریم سر اصل مطلب...همونطور که می‌دونین یکی از چالش‌هایی که در فرایند تحلیل اپلیکیشن‌های اندرویدی، Reverse و Pentestشون داریم، وجود الگوریتم‌های امنیتی‌ایه که هدف از بکارگیری‌شون این بوده که کارو برای ما (شخصی که میخواد Reverse یا Pentest کنه) سخت کنن و یکی از اصلی‌ترین چالش‌های ما هم Bypass کردن همین الگوریتم‌هاست.اگه با این الگوریتم‌ها آشنا نیستین پست زیر میتونه براتون مفید باشه:البته پست زیر برای 2-3 سال پیشه و یکم نیاز به ویرایش داره، ولی خب بازم برای یادگیری کلیات خوبه. https://virgool.io/fboard/14-%D9%86%DA%A9%D8%AA%D9%87-%D8%A8%D8%B1%D8%A7%DB%8C-%D8%A8%D9%87%D8%A8%D9%88%D8%AF-%D8%A7%D9%85%D9%86%DB%8C%D8%AA-%D8%AF%D8%B1-%D8%A7%D9%BE%D9%84%DB%8C%DA%A9%DB%8C%D8%B4%D9%86%E2%80%8C%D9%87%D8%A7%DB%8C-%D8%A7%D9%86%D8%AF%D8%B1%D9%88%DB%8C%D8%AF%DB%8C-vm8fzcs6zfh7 مثلا فرض کنین می‌خوایم ترافیک Network اپلیکیشن رو بگیریم ولی نتونیم، یا ترافیک رو گرفتیم ولی به صورت Encrypt شده ارسال/دریافت میشه و محدودیت‌های این مدلی دیگه، خب چکار کنیم؟ دو تا روش کلی داره:یکی اینکه بیایم اپلیکیشن رو Patch کنیم، یعنی بیایم دیکامپایلش کنیم و اون قسمتی از Code که به فرض رمزنگاری ترافیک رو انجام میده حذف کنیم و دوباره خروجی بگیریم (این میشه همون دوره‌‌‌ای که اول پست بهش اشاره کردم، البته توی اون دوره تمرکز اصلی روی کدهای Java بود و به Native Code فقط یه اشاره کوچیک شد. توی این رشته پست‌ها قراره Native هم کار کنیم)در روش دوم می‌تونیم اون بخشی که برای ما (Pentest و Reverse) محدودیت ایجاد کرده رو Hook کنیم. این کار معمولا با Frida انجام میشه. اگه با Frida و Hook کردن آشنا نیستین لینک‌های زیر میتونه براتون مفید باشه:Android Pentesting With FridaMobile App Testing With Fridaخیلی خلاصه‌ش اینجوری میشه که در فرایند Hook، به جای اینکه مستقیم فایل اپلیکیشن را تغییر بدیم (کاری که در Patch یا کرک انجام می‌دادیم)، بخشی از کد که می‌خوایم تغییر کنه رو در لحظه اجرا و از روی Memory تغییر میدیم. مثلا فرض کنید یک فانکشن تعریف شده که بررسی می‌کنه کاربر کلمه عبور رو صحیح وارد کرده یا نه. در حالت پیشفرض این فانکشن باید فقط در صورتی اجازه لاگین بده که کلمه عبور صحیح وارد بشه، ما با Hook کردن میایم این فانکشن رو (در لحظه اجرا) تغییر میدیم که در هر صورت اجازه لاگین بده.پس هوک کردن یه جورایی مثل همون Patch کردن می‌مونه، با این تفاوت که تغییر در لحظه اجرا انجام میشه و چیزی از روی فایل تغییر نمی‌کنه.اگه براتون سواله که Patch کردن بهتره یا Hook کردن، بستگی به پروژه و رفتاری داره که می‌خوایم Bypassش کنیم، خیلی وقتا هست که بخشی از کار رو با Patch کردن انجام میدیم بخشی دیگه رو با Hook (ولی خب روی هم رفته در فرایند Reverse و Pentest از Hook معمولا بیشتر استفاده میشه)برای آشنا شدن با Hook شاید پست زیر هم براتون جالب باشه: https://virgool.io/@emad_abedini/%D8%B1%D9%88%D8%B4-intercept-%D8%AA%D8%B1%D8%A7%D9%81%DB%8C%DA%A9-%D8%AF%D8%B1-%D8%A7%D9%BE%D9%84%DB%8C%DA%A9%DB%8C%D8%B4%D9%86-%D9%87%D8%A7%DB%8C-flutter-%DB%8C-b8i99jgcdmyh راستی اینم بگم که برای Hook کردن اپ‌های اندرویدی، از ابزارهای دیگه‌ای هم میشه استفاده کرد، مثلا:-- استفاده از Objection (خودش از Frida استفاده می‌کنه، در واقع یکسری اسکریپت آماده داره که فرایند هوک رو خیلی ساده‌تر می‌کنه، ولی طبیعتا همیشه جواب نمیده)-- استفاده از r2frida (اینم همونطور که مشخصه از Frida استفاده می‌کنه، در واقع میاد Frida و Radere2 رو ترکیب می‌کنه، ابزار خیلی خوبیه، با این کار می‌کنیم جلوتر)-- استفاده از Xposed Framework (اینم ابزار بدی نیست، قدیما خیلی ازش استفاده می‌شد، ولی اصلا در حد Frida نیست، یکسری مشکلات داره مثلا با نسخه‌های جدید اندروید مشکل داره، برای کار با Native Code خیلی محدودیت داره، فقط با دیوایس‌های روت شده کار می‌کنه و...)پس تا اینجای کار رسیدیم به این که یکی از روش‌های Bypass مکانیزم‌های امنیتی اینه که اون بخشی از کد که باعث محدودیت شده رو hook کنیم و در حال حاضر Frida بهترین ابزاریه که برای این منظور استفاده میشه.در واقع Frida اول میاد با استفاده از ptrace به Processی که می‌خوایم (اپلیکیشنی که میخوایم Hookش کنیم)، Attach میشه، بعدش Gadget خودش رو که برای اندروید یک فایل باینری به اسم libfrida-gadget.so هست رو بهش Inject می‌کنه، مرحله آخر هم میاد با V8 اسکریپتی که بهش دادیم رو اجرا می‌کنه.نحوه استفاده از Frida معمولاً یکی از سه حالت زیر میشه:تو حالت اول، اپلیکیشن اومده از الگوریتم‌، کتابخونه و روش‌های عمومی برای پیاده‌سازی مکانیزم‎‌های امنیتی استفاده کرده، توی این حالت ما هم می‌تونیم با Script‌های عمومی Frida که بیشترشون رو هم میشه از https://codeshare.frida.re پیدا کرد، Bypass رو انجام بدیم.حالت دوم، اپلیکیشن اومده به جای اینکه کامل از الگوریتم‌ و روش‌های عمومی استفاده کنه، تا حدودی تغییرشون داده (معمولا میان به جای import مستقیم کتابخونه‌های public، از سورس‌کدشون با کمی تغییرات جزئی استفاده می‌کنن). تو این حالت نیازه که با Frida و نحوه کار باهاش آشنایی بیشتری داشته باشیم تا بتونیم با یکسری تغییرات جزئی روی همون اسکریپت‌های عمومی، Bypass رو انجام بدیم.معمولا تو این حالت الگوریتمی که استفاده شده همون الگوریتم‌های Publicه، فقط اسم کلاس و توابع تغییر داده شده و بنابراین برای Bypass هم تغییری که نیازه روی اسکریپت‌های عمومی انجام بدیم خیلی جزئیه.حالت سوم، مکانیزمی که اپلیکیشن استفاده کرده به صورت اختصاصی پیاده‌سازی شده، تو این حالت امکان Bypass با استفاده از Scriptهای آماده‌ (حتی با اعمال تغییرات) وجود نداره و نیازه که ما هم اسکریپت Bypass اختصاصی متناسب با همون الگوریتم رو داشته باشیم.توی این پست و در ادامه، ما روی حالت سوم کار می‌کنیم. (در کل چه توی این پست و چه پست‌های بعدی ادامه‌ی این پست، از هیچ اسکریپت آماده‌ای استفاده نمی‌کنیم)شاید این سوال پیش بیاد که چرا همیشه از روش سوم استفاده نکنیم که خیالمون راحت باشه هر الگوریتمی رو می‌تونیم Bypass کنیم؟ جواب اینه که واقعا لازم نیست. وقتی اپلیکیشن اومده از کتابخونه‌ و روش‌های عمومی استفاده کرده و میشه راحت با اسکریپت عمومی Bypassش کرد، چرا بخوایم وقتمون رو هدر بدیم اسکریپت اختصاصی بنویسیم!؟ پس هر چیزی به جای خودش و در صورت نیاز.انواع Hook در اپلیکیشن‌های اندرویدی :زمانی که از Hook کردن اپلیکیشن‌های اندرویدی صحبت می‌کنیم، معمولا با یکی از این دو مورد سر و کار داریم:Java MethodsNative Methodsمورد اول که مشخصه، دومی هم یعنی کدی که می‌خوایم Hookش کنیم، با C یا ++C نوشته شده.حالا که صحبت از Native Methods شد، اینم در نظر بگیرین که اکثر اسکریپت‌های آماده‌‌ی Frida برای Java Methods نوشته شدن و اگه اپلیکیشن و یا اون بخشی که می‌خوایم Hookش کنیم با C یا ++C پیاده‌سازی شده باشه، دیگه جواب نمیدن.شاید براتون سوال پیش بیاد الان دیگه کی میاد با C یا ++C اپلیکیشن اندرویدی بزنه!؟زمانی که به فرض با فلاتر اپلیکیشن اندرویدی توسعه میدین، کدها در نهایت به یک فایل so. تبدیل میشن.اکثر بازی‌ها با C یا ++C توسعه داده شدن. اپلیکیشن‌هایی زیادی هستن که برای بخش‌هایی که نیاز به Performance بیشتری داشتن، از C و ++C استفاده کردن.بعضی از اپلیکیشن‌ها برای پیچیده‌تر کردن فرآیند Reverse Engineering بخش‌هایی از کدشون رو با C زدن. مثلا همین الان یه تعدادی از اپلیکیشن‌های خصوصا مرتبط با پرداخت داخلی، بخش رمزنگاری ترافیک Network اشون با ++C زده شده.سوال دیگه‌ای که ممکنه براتون پیش بیاد اینه که چرا Reverse کردن C و ++C نسبت به زبانی مثل Java و Kotlin سخت‌تره؟ همونطور که می‌دونید خیلی از زبان‌ها مثل Java، Kotlin و #C، در زمان کامپایل به زبان میانی (Intermediate Language) تبدیل و بعد در زمان اجرا به زبان ماشین ترجمه میشن، ولی زبان‌هایی مثل C و ++C همون مرحله‌ی کامپایل به زبان ماشین (01010110011) تبدیل میشن. یکی از دلایلی که Performance در زبان‌های سطح پایین بیشتره هم همینه، شما با اسمبلی کد بزنین سرعتتون خیلی بیشتره تا حتی با C بزنین، در همین خصوص شاید این لینک براتون جالب باشه:افزایش 94 برابری عملکرد FFMpeg با استفاده از کدهای اسمبلیحالا این تفاوت چه ربطی به فرآیند Reverse Engineering داره؟ وقتی اپلیکیشنی با زبانی مثل Java زده شده، خروجی‌ای که داریم به زبان میانی هست که این باعث میشه Decompile کردن و برگردوندنش به کد سطح بالاتر خیلی ساده‌ باشه. ولی برخلافش در زبان‌هایی مثل C و ++C، از اونجایی که همون اول کار بعد از کامپایل، خروجی به زبان ماشین (باینری) تبدیل میشه، اگه بخوایم برش گردونیم به زبان سطح بالا اول باید با استفاده از یک Disassembler به اسمبلی تبدیل بشه بعدش با یک Decompiler تا جای امکان به کد سطح بالاتر (شبیه به C)، که همین باعث میشه Reverse کردن فایل‌های باینری پیچیدگی‌های خاص خودش رو داشته باشه.بریم چیزایی که گفته شد رو عملی کار کنیم:من برای این پست و احتمالا 3-4 پست بعدی، از Sample‌های MASTG استفاده می‌کنم. خوبی این Sampleها اینه که به‌طور خاص به هدف آموزش و آشنایی با مفاهیم امنیت طراحی شدن، تو بیشتر کتاب‌ و آموزش‌های مرتبط با امنیت اندروید هم از همین نمونه‌ها استفاده شده، ما هم فعلا با همین نمونه‌ها میریم جلو...1- اول فایل اپلیکیشن رو از لینک زیر دانلود می‌کنیم:https://github.com/OWASP/owasp-mastg/blob/master/Crackmes/Android/Level_02/UnCrackable-Level2.apk2- اپلیکیشن رو توی Emulator نصب و اجرا می‌کنیم. (بعدا یه پست در خصوص نحوه راه‌اندازی اصولی Emulator برای تحلیل بدافزارهای اندرویدی آماده می‌کنم و در خصوص موضوعاتی مثل انتخاب شبیه‌ساز مناسب، ایزوله کردن شبیه‌ساز، ابزارهای تحلیل، شبیه‌سازی اینترنت، Snapshot و .. صحبت می‌کنم. ولی فعلا اینجا تو این مثال چیزی که روش کار می‌کنیم بدافزار نیست و تنها چیزی که لازم داریم اینه که روی هر Emulatorی که باهاش راحت‌ترین نصب و اجراش کنین)3- همونطور که می‌بینین اپلیکیشن Root detection داره و اجازه بالا اومدن سر دیوایس روت شده رو نمیده، بریم فعلا این بخش رو Bypass کنیم:Root Detection4- اپلیکیشن رو با JADX دیکامپایل می‌کنیم و بخشی از متن Alert Dialog بالا رو توی کدهای استخراج شده سرچ می‌کنیم (با هدف پیدا کردن بخشی از کد که وظیفه بررسی Root بودن دستگاه رو بر عهده داره):اپلیکیشنی که داریم کار می‌کنیم، سورس‌کدش خیلی کمه و خیلی راحت با نگاه کردن هم میشه هر چیزی رو پیدا کرد و در واقع نیازی به سرچ توی کد نیست. در کل خیلی از توضیحات و روش‌هایی که توی این رشته پست‌ها گفته میشه ممکنه برای Sampleی که روش کار می‌کنیم اصلا نیازی نباشه، ولی هدف اینه با روش انجام کار در شرایط واقعی آشنا بشیم. نسبت به اینکه هر اپلیکیشن با چه زبان و فریمورکی توسعه داده شده، باید از Decompiler مخصوص به خودش استفاده کرد، به فرض اگه اپلیکیشن با Flutter توسعه داده شده بود JADX نمی‌تونست بخش اصلی (فایل libapp.so) رو دیکامپایل کنه.برای اینکه ببینین هر زبان و فریمورک رو با چه Decompilerهایی میشه دیکامپایل کرد، فقط کافیه یه سرچ کوچیک کنین. (البته تو این رشته پست‌ها با 2-3 تا از بهتریناشون آشنا میشیم...)خب همونطور که می‌بینید بخشی از متن Alert Dialog رو سرچ کردیم و رسیدیم به یک if که در صورت true بودن، مقدار Root detected رو به متد a ارسال می‌کنه:جستجوی بخشی از متن Dialog Box در کد استخراج شدهارسال مقدار Root detect به تابع a (در صورت برقراری شرط)اینم فانکشن a ، که build و show کردن Alert Dialog رو انجام میده. پس در صورتی که if بالا (کادر قرمز) برقرار باشه، مقدار Root Detected به این فانکشن ارسال میشه تا همراه با Message زیر به صورت یک Alert Dialog به کاربر نمایش داده بشه:ایجاد و نمایش Alert Dialog  5- حالا باید ببینیم if ی که true شده و باعث شده این Alert Dialog فراخوانی بشه چی بوده. همونطور که می‌دونین توی JADX اگه روی فراخوانی هر تابع کلیک کنیم به خود تابع میرسیم.پس اینجوری میرسیم به توابعی که داخل if استفاده شدن:کلاسی که بررسی و تشخیص روت بودن دستگاه رو بر عهده داره3 متد بالا برای تشخیص روت بودن دستگاه استفاده شدن:متد اول میاد دنبال فایلی با نام su (فایل مرتبط با ابزارهای مدیریت Root) می‌گرده.متد دوم میاد Build.TAGS رو چک می‌کنه (اکثر دیوایس هایی که از Custom ROM استفاده می‌کنن یا دیوایس‌هایی که به هدف تست و توسعه تولید شدن مقدار Build.TAGS اشون برابر با test-keys هست)متد سوم هم میاد دنبال مسیر و فایل‌هایی می‎گرده که معمولا دیوایس‌های روت شده این فایل‌‌ و مسیر‌ها رو دارن. از اونجایی که توی شرطی که داریم (کادر قرمز بالا) بین این 3 متد OR شده، پس حتی اگه return یکی از این methodها true بشه، دستگاه روت شده تشخیص داده میشه.6- خب پس تنها تغییری که نیازه انجام بدیم اینه که مقدار return هر 3 تا متد همیشه و در هر صورت برابر با false بشه. اگه دوره‌ای که اول پست صحبتش بود رو دیده باشین، نمونه‌های این مدلی زیاد کار کردیم توی دوره، فقط فرقی که اونجا داشت این بود که اپلیکیشن رو Patch می‌کردیم (سورس‌کد رو تغییر می‌دادیم و از سورس‌کد تغییر داده شده Build می‎‌گرفتیم) ، ولی الان اینجا می‌خوایم بدون تغییر تو فایل اپلیکیشن، تغییراتی که نیاز داریم رو لحظه اجرا شدن (از روی Memory) تغییر بدیم.حالا چجوری میتونیم برای Frida اسکریپتی بنویسیم که این تغییرات رو انجام بده، خیلی راحت‌تر از هر چیزی که فکرشو کنین:قبلش بگم که مستندات کامل کار با Frida رو می‌تونین از دو لینک زیر مطالعه کنین: (پیشنهادم اینه که یک دور روزنامه‌ای بخونینش تا با امکاناتی که داره آشنا بشین)https://frida.re/docs/android/https://frida.re/docs/javascript-api/لینک GitHub سورس‌کدی که کار می‌کنیم رو آخرای پست براتون گذاشتم که بتونین راحت‌تر استفاده کنین.7- اسکریپتی که برای هوک کردن و تغییر مقدار return نیاز داریم همین چند خط کد زیر میشه:خط 1، طبق مستندات Frida اسکریپت‌مون رو داخل این فانکشن تعریف می‌کنیم تا در زمان و به صورت مناسب تحویل (Java virtual machine) JVM بشه. خط 3 کلاسی که قصد Hook کردنش رو داریم مشخص کردیم که از اینجا (کادر قرمز پایین) برش داشتیم:PackageName.ClassNameدر خط 6 هم متدی که می‌خوایم Hook کنیم رو مشخص ‌می‌کنیم (کادر سبز شماره 1)، خط 10 مقداری که در حال حاضر (بدون هوک) متد a داره return می‌کنه رو log می‌گیریم (نیازی نیست، فقط میخوایم ببینیم مقدار return اصلی چی بوده). خط 14 و 15 هم مقدار false رو به عنوان return برمی‌گردونیم (یعنی میگیم return متد a تحت هر شرایطی false باشه)خب حالا باید همین اسکریپت رو برای متد b و c (کادر سبز رنگ شماره 2 و 3) هم انجام بدیم دیگه، پس میشه به این صورت:شاید براتون سوال پیش بیاد این اسکریپت رو می‌شه خیلی کوتاه‌تر و مرتب‌تر هم نوشت! درسته.اسکریپت نهایی می‌تونه به صورت Refactor شده‌ی زیر باشه: 8- حالا بریم اسکریپت رو اجرا کنیم ببینیم نتیجه چی میشه:اگه با روش اجرای اسکریپت‌های Frida آشنا نیستین، تو این لینک توضیح داده شده.خب همونطور که مشخصه، متد b و c بدون تغییر از سمت ما، مقدار false رو return کردن ولی از متد a مقدار true برگشته و همین باعث شده شرط بررسی روت بودن دستگاه true شه و اجازه اجرا شدن اپلیکیشن داده نشه (Alert Dialog نمایش داده بشه)، ولی ما اومدیم هوکش کردیم و مقدار return هر 3 متد رو false کردیم و بنابراین الگوریتم Root Detection الان Bypass شده:قبل از اینکه بریم سراغ ادامه کار، برای تمرین بیشتر می‌تونین اپلیکیشن رو Debug Mode بیارین بالا و بعد Debug Mode Detection رو Bypass کنین، یعنی این قسمت:یکی از روش‌های Bypassش دقیقا به همین روشی میشه که Root Detectionش رو Bypass کردیم.بریم یه قسمت دیگه رو هم Hook کنیم...بعد از اینکه Root Detection رو Bypass کردیم و صفحه اول اپلیکیشن بالا اومد، یه Edit Text داریم با یک Button به اسم Verify، زمانی که روی Verify کلیک می‌کنیم پیغام زیر نمایش داده میشه که نشون میده مقداری که وارد کردیم اشتباهه (البته از روی UI هیچوقت نمیشه فانکشن رو پیش‌بینی کرد ولی اینجا با این فرض میریم جلو)، پس بریم مقدار صحیح رو پیدا کنیم.1- مشابه با کاری که بخش اول انجام دادیم، اول باید اون بخشی از کد که باعث فراخوانی Alert Dialog شده رو پیدا کنیم، پس میایم بخشی از متنش رو توی سورس‌کد سرچ می‌کنیم و می‌رسیم به این قسمت:ایجاد و نمایش Alert Dialog همون‌طور که مشخصه، اگه شرط (کادر نارنجی) true باشه، کادر سبز و اگه false باشه، کادر قرمز به‌صورت یک Alert Dialog به کاربر نمایش داده میشه. در واقع تا اینجای کار هنوز نمی‌دونیم این شرط دقیقا داره چه چیزی رو چک میکنه، ولی فرض می‌کنیم میاد مقدار وارد شده برای EditText رو با مقداری که به عنوان مقدار صحیح در نظر گرفته شده، مقایسه می‌کنه.2- خب حالا باید شرط (کادر نارنجی بالا) که باعث فراخوانی این Alert Dialog شده رو بررسی کنیم. که میرسیم به:3- اینجا قبل از متد bar از کلمه‌کلیدی native استفاده شده، زمانی که موقع تعریف یک متد از native استفاده میشه، یعنی این متد در زبانی غیر از Java (معمولاً C یا ++C) پیاده‌سازی شده، در واقع کلمه‌کلیدی native از طریق JNI (Java Native Interface) برای ارتباط بین Java و متدهای Native استفاده میشه.پس الان مسیری که پیش رو داریم مشخص شد:اول فایل C یا ++C ی که متد bar رو پیاده‌سازی کرده پیدا کنیم.فایل رو دیکامپایل کنیم.بخشی از کد که نیاز به تغییر داره رو هوک کنیم.اینم در نظر داشته باشین که برخلاف روش قبلی، الان با فایل باینری سروکار داریم که روش دیکامپایل و هوک متفاوتی داره.4- بریم فایل رو پیدا کنیم...اپلیکیشنی که داریم کار می‌کنیم فقط یک فایل باینری داره، پس مشخصه که تابع bar هم توی همین فایل پیاده‌سازی شده، ولی خیلی وقت‌ها اپلیکیشن‌ها از چند فایل باینری استفاده کردن، تو همچین حالتی برای اینکه متوجه بشیم کدی که می‌خوایم هوکش کنیم تو کدوم فایل پیاده‌سازی شده، کافیه موارد زیر رو توی سورس‌کد سرچ کنیم (معمولا فایل‌های باینری با یکی از این روش‌ها تو Java بارگذاری می‌شن)System.loadLibrary()System.load()ClassLoader5- همونطور که مشخصه، این اپلیکیشن با روش اول فایل Nativeش رو بارگذاری کرده:یک نکته رو دقت کنین، اینجا گفته شده foo رو بارگذاری کن، ولی ما باید دنبال فایل libfoo.so باشیم. چرا؟ دلیل اینجاست که JVM بر اساس نوع سیستم عامل، prefixes و extension اضافه می‌کنه. مثلا اگه ویندوز بود دنبال foo.dll می‌گشت، اگه macOS بود دنبال libfoo.dylib می‌گشت. الان ما داریم روی اندروید (که مبتنی بر لینوکسه) کار می‌کنیم، توی لینوکس JVM میاد یه lib به اول و یه so. به آخر مقدار وارد شده اضافه می‌کنه، پس میشه libfoo.so 6- خب حالا بریم فایل libfoo.so رو توی ساختار فایل پیدا کنیم و بعد دیکامپایلش کنیم:ساختار فایل اپلیکیشنی که روش کار می‌کنیمهمونطور که می‌بینید توی فولدر lib برای هر معماری، فایل باینری مخصوص اون معماری قرار داده شده. حالا ما روی کدوم کار کنیم؟ باید ببینیم دیوایس یا Emulatorی که روش کار می‌کنیم چه معماری‌ای داره، الان اکثر دیوایس‌های اندرویدی ARM64 هستن ولی اگه دارین روی Emulator کار می‌کنین اکثرا x86 یا x86_64 هستن (البته اگه با Frida کار می‌کنین پس معماری دیوایسی که روش Frida اجرا شده رو هم میدونین قطعا، چون باید بر اساس معماری Frida Server رو دانلود کرده باشین).برای تشخیص معماری دیوایس اندرویدی، یکی از روش‌ها اینه که کد زیر رو با adb اجرا کنید:adb shell getprop ro.product.cpu.abi من الان دارم از یک Emulator روی Nox Player که نسخه 7 اندروید و معماری x86 داره استفاده می‌کنم. ولی از اونجایی که از ARM translation پشتیبانی می‌کنه، می‌تونم از armeabi-v7a هم استفاده کنم. حالا کدوم بهتره؟ اگه اپلیکیشنی که دارین روش کار می‌کنین هم معماری arm رو داشت و هم x86، بهتره arm رو انتخاب کنین (با فرض اینکه از NEON و Thumb mode استفاده نشده باشه، arm نسبت به x86 راحت تره)ولی ما اینجا در ادامه با x86 رفتیم جلو که اگه با اپلیکیشنی مواجه شدین که فقط x86 داشت فکر نکنین خیلی چیز عجیب و پیچیده‌ایه...7- برای Decompile من از Radare2 استفاده کردم ولی شما می‌تونین از هر دیکامپایلری که باهاش راحت‌تر هستین استفاده کنین (Radare2، Ghidra و JEB رو پیشنهاد می‌کنم)فانکشن‌هایی که داریم به این صورته:همونطور که مشخصه یکی از functionهایی که داره همون تابع bar هست که دنبالش بودیم، که در 0x00000f60 قرار گرفته، بریم ببینیم چی داره داخلش:بخشی از تابع bar8- خب حالا از کجا متوجه بشیم کدوم قسمت رو باید تغییر بدیم؟ لازمه از خط اول شروع کنیم به تحلیل کد؟ بیشتر وقتا همچین چیزی اصلا لازم نیست، کافیه نحوه پیاده‌سازی اون بخشی از کد که میخوایم hookش کنیم رو در نظر بگیریم و دنبال تابعی باشیم که به اون الگوریتم مربوط میشه. حالا این یعنی چی؟ما یک Edit Text داریم که از کاربر ورودی میگیره و نسبت به اینکه ورودی صحیح باشه یا غلط، نتیجه رو با یک Alert Dialog نشون میده. خب تو همچین حالتی الگوریتم رو به چه صورت میشه فرض کرد؟ اینکه دو تا مقدار داریم، یکی مقداری که کاربر وارد میکنه، یکی هم مقدار صحیح، احتمالا میاد این 2 تا مقدار رو با هم مقایسه می‌کنه و در صورتی که برابر باشن مقدار مشخصی رو برمیگردونه. خب حالا با همین فرض، کافیه تابعی رو پیدا کنیم که میتونه این کارو انجام بده. همونطور که مشخصه (کادر نارنجی پایین)، اینجا از تابع strncmp استفاده شده که برای مقایسه دو string استفاده میشه.یه نگاهی به مستندات strncmp بندازیم:https://cplusplus.com/reference/cstring/strncmp/int strncmp ( const char * str1, const char * str2, size_t num );-- آرگومان اول (str1): رشته اول که میخوایم با رشته دوم مقایسه بشه.-- آرگومان دوم (str2): رشته دوم که با رشته اول مقایسه میشه.-- آرگومان سوم (num): تعداد کاراکترهایی که باید مقایسه بشن.اگه مثلا num رو برابر 5 تعریف کنیم، میاد 5 کاراکتر اول از هر دو رشته رو مقایسه می‌کنه. نتیجه‌ی تابع هم به این صورت میشه:-- اگه رشته‌ها یکسان باشن، مقدار 0 برگشت داده میشه. (بیشتر توابع مقایسه‌ی دو string در زبان C، زمانی که مقادیر برابر باشن مقدار صفر return میشه.)-- اگه رشته اول از دوم کوچک‌تر باشه، مقدار منفی برگشت داده میشه.-- اگه رشته اول از دوم بزرگ‌تر باشه، مقدار مثبت برگشت داده میشه.* و اینکه طبق مستندات،مقایسه بر اساس ASCII انجام میشه، بنابراین Case-sensitive هست.9- حالا باید مشخص کنیم کدوم بخش و چه چیزی از strncmp رو باید هوک کنیم؟اگه بخشی که داریم کار می‌کنیم مثلا صفحه لاگین بود، معمولا فقط برامون مهمه که بتونیم لاگین کنیم. توی اون حالت می‌تونستیم مقدار برگشتی از strncmp رو برابر با 0 بذاریم. ولی اینجا هدف اینه که مقدار صحیح رو پیدا کنیم (نه اینکه فقط حالتی رو به وجود بیاریم که بگه مقدار صحیح وارد شده، خود مقدار صحیح رو میخوایم)چکار کنیم؟ کافیه زمان فراخوانی strncmp ، آرگومانی که مربوط به مقدار صحیح (مقدار ثابت) میشه رو log بگیریم. حالا توی strncmp آرگومان اول اون مقداریه که داینامیک از کاربر گرفته میشه یا آرگومان دوم؟ طبق مستندات این تابع، در صورتی که هدف مقایسه دو رشته برابر (مقدار برگشتی 0) باشه، ترتیب آرگومان‌های اول و دوم مهم نیست. ولی طبق کد، رجیستر eax قبل از اینکه به صورت پارامتر به تابع ارسال بشه، اول با دستور   lea eax, [s2]  مقداردهی شده، یعنی با یک مقدار ثابت (آدرس متغیر s2) پر شده، پس به احتمال خیلی زیاد eax همون مقداری هست که به صورت ثابت (همون مقدار صحیح) به صورت hardcode در کد وجود داره.البته اینجا ما میتونیم هر 3 آرگومانش رو log بگیریم چون بالاخره یکیش میشه همون مقدار ثابت، توضیحی که دادم برای تأکید بر اهمیت تحلیل اسمبلی بود.اسکریپتی که نیاز داریم به این صورت میشه: لینک Github کدهای که تو پروژه کار کردیم رو آخر پست گذاشتم.قبل از اینکه اسکریپت رو توضیح بدم، نیازه یکم با تابع android_dlopen_ext آشنا بشیم. توی لینوکس از تابع dlopen برای بارگذاری کتابخونه‎‌های داینامیک (منظور کتابخونه‌هایی که در لحظه اجرا اضافه میشن) استفاده میشه که همین تابع با یکسری تغییرات مثل بهینه‌سازی و افزایش امنیت بارگذاری کتابخونه‌ها، توی اندروید به تابع android_dlopen_ext تغییر کرده، در واقع مدیریت کتابخونه و فایل‌های باینری‌ای که یک اپلیکیشن اندرویدی بارگذاری می‌کنه رو این تابع بر عهده داره.اینجا خط 1 گفتیم تابع android_dlopen_ext رو پیدا کن. null ای که گذاشتیم یعنی داریم میگیم این تابع رو بین همه‌ی کتابخونه‌های بارگذاری شده بگرد.البته میتونیم به جای null اسم خود کتابخونه‌ای که این تابع رو پیاده‌سازی کرده رو هم بذاریم ولی از اونجایی که تو نسخه‌های مختلف اندروید اسم این کتابخونه فرق میکنه، بهتره null بذاریم که بین کل کتابخونه‌های بارگذاری شده بگرده.خط 3 یک boolean تعریف کردیم که هر موقع libfoo بارگذاری شد از این طریق متوجه بشیم. خط 5 تا 21 به android_dlopen_ext اومدیم Attach شدیم، اینجا دو تا تابع داریم، یکی OnEnter و یکی OnLeave. به محض اینکه android_dlopen_ext اجرا میشه OnEnter فراخوانی میشه و بعد در زمانی که android_dlopen_ext مقداری رو Return کنه OnLeave فراخوانی میشه. در واقع android_dlopen_ext هر بار که call میشه یک کتابخونه رو بارگذاری می‌کنه (تو این مرحله OnEnter فراخوانی میشه) و بعد از پایان تابع و Return مقدار برگشتی، OnLeave فراخوانی میشه. حالا ما تو خط 7 اومدیم گفتیم آرگومان ورودی android_dlopen_ext رو بگیر و اگه برابر با libfoo بود، متغیر libfoo_loaded رو true کن. (در واقع این حلقه اونقدر تکرار میشه تا کل کتابخونه‌ها بارگذاری بشن، که یکی از کتابخونه‌ها میشه libfoo که دنبالش هستیم) سوال دیگه‌ای که ممکنه پیش بیاد اینه که دلیلی داشته آرگومان ورودی android_dlopen_ext رو توی OnEnter گرفتیم؟ بله، چون توابع توی OnEnter فراخوانی میشن و فقط توی OnEnter ه که میتونیم args رو بگیریم. اگه فرض کنیم توی OnLeave هم به args دسترسی داشتیم، دیگه نیازی به تعریف libfoo_loaded نبود.خط 12 اومدیم گفتیم اگه libfoo فراخوانی شده بود Base Addressش رو بگیر. (تا فراخوانی نشه که نمی‌تونیم بهش دسترسی داشته باشیم، پس قبلش libfoo_loaded رو چک می‌کنیم که true باشه)خط 23 یک تابع تعریف کردیم و گفتیم به Base Address ی که از libfoo پیدا کردی (مرحله قبل)، مقدار Offset مربوط به strncmp که میشه 0x00000ffb رو اضافه کن (که دقیقا به آدرس strncmp برسیم) و بهش Attach شو. آدرس offset مربوط به strncmp رو هم که از اینجا برداشتیم:خط 25 اومدیم گفتیم زمانی که strncmp فراخوانی میشه مقدار eax (آرگومان ورودی تابع که مقدارش به صورت ثابت توی کد تعریف شده) رو بگیر و توی Log نشونش بده.و در آخر، خط 18 تابعی که خط 23 تعریف کردیم رو فراخوانی می‌کنیم.خب، حالا بریم اسکریپت رو اجرا کنیم.دقت کنید که اسکریپت اول و دوم باید هم زمان (هر دو در یک فایل) اجرا بشن، که اسکریپت اول Root Detection رو Bypass کنه و اسکریپت دوم مقدار ورودی صحیح رو log کنه.10- بعد از اجرای اسکریپت، به صورت پیش‌فرض هیچ لاگی نمایش داده نمیشه. چرا؟ چون تابع strncmp به طور پیش‌فرض فراخوانی نمیشه، فقط زمانی فراخوانی میشه که مقداری رو مقایسه کنه. بنابراین، لازمه مقداری رو وارد کنیم و بعد Verify رو کلیک کنیم تا strncmp فراخوانی بشه.اما، بعد از وارد کردن مقدار و کلیک روی Verify بازم لاگی ثبت نمیشه. چرا !؟بریم باز یه نگاهی به کد بندازیم...همونطور که مشخصه (کادر نارنجی شماره 1) در صورتی که مقدار موجود در رجیستر eax یعنی همون مقداری که وارد می‌کنیم برابر با 0x17 (که برابر با 23 میشه) نباشه ZF (Zero Flag) برابر با 0 میشه و زمانی هم که ZF برابر با صفر بشه، EIP به آدرس جدید یعنی 0x1007 منتقل میشه (از روی strncmp رد میشه)، خب پس برای اینکه jump انجام نشه باید طول مقداری که وارد می‌کنیم برابر با 23 باشه.توی کادر نارنجی شماره 2 در صورتی که مقادیر برابر باشن و strncmp مقدار صفر رو برگردوند.ه باشه، ZF برابر با 1 میشه و EIP به آدرس 0x101e تغییر می‌کنه (هدفی که ما داریم)نمایش به صورت گراف 11- پس برای فراخوانی strncmp لازمه مقداری که وارد می‌کنیم دقیقا 23 کاراکتر باشه:12- خروجی اسکریپت بعد از کلیک Verify به صورت زیر میشه:13- در نهایت مقداری رو که به عنوان مقدار صحیح log گرفتیم تست می‌کنیم:تست ورودی با مقدار صحیح رسیدیم به پایان بخش اول...این پست خیلی طولانی‌تر از چیزی شد که انتظارش رو داشتم، ولی از طرفی اگه دو بخشش می‌کردیم موضوع ناقص می‌موند.برنامه اینه که این پست، حدود 11-12 بخش دیگه هم داشته باشه. هدف از بخش اول (همین پست)، آشنایی اولیه با مفاهیم هوک کردن بود.در روند انجام مراحلی که کار کردیم، سعی کردم سوالاتی که ممکنه براتون پیش بیاد رو جواب بدم. چند موضوع دیگه که شاید گفتنش بد نباشه اینه که:روشی که برای انجام مراحل این پست کار کردیم به هدف یادگیری مفاهیم بود، وگرنه میشد خیلی سریع‌تر هم به جواب رسید. مثلاً:-- توی بخش دوم، رشته‌ای رو که با هوک کردن به دست آوردیم، می‌شد خیلی راحت با بررسی استاتیک هم پیدا کرد.-- یا در بخش Root Detection، می‌شد با یک خط Bypass کرد.و سوال دیگه‌ای که ممکنه براتون پیش اومده باشه اینه که:نوشتن اسکریپت برای Frida واقعاً این‌قدر ساده‌ست؟اگه نگاهی به محبوب‌ترین اسکریپت‌های Frida CodeShare بندازید، بعد از این پست احتمالاً 70-80٪ از کدشون رو متوجه بشین، اما واقعیت اینه که نه، هوک کردن همیشه هم به این سادگی نیست. در واقع به نحوه پیاده‌سازی مکانیزم امنیتی‌‌ای که میخوایم hookش کنیم بستگی داره، مثلا:گاهی از Anti-Tampering استفاده شده.بعضی وقتا کلاس‌ها و توابع در زمان اجرا ایجاد میشن.خیلی وقتا سورس‌کد شدیدا Pack یا Obfuscate شده.ممکنه از تکنیک‌های Reflection استفاده شده باشه.خیلی وقتا الگوریتم‌های تشخیص Frida پیاده‌سازی شده.و کلی چالش دیگه که میتونه فرایند Pentest رو پیچیده کنه. خبر خوب اینکه نگران نباشین، همونطور که گفتم قراره این پست تا 11-12 بخش دیگه ادامه داشته باشه و کلی از چالش‌های مرتبط با Reverse و Hook (که فقط بخشی ازشون به هوک کردن مربوط میشه) رو بررسی می‌کنیم.راستی بازم بگم که اگه در زمینه باگ‌بانتی و Pentest اپلیکیشن‌های داخلی کار می‌کنین، حداقل 80% اشون (شامل بانک‌ها، مرتبط با پرداخت، سازمان‌ها و ...) اکثر مکانیزم‌های امنیتی‌ای که پیاده‌سازی کردن کتابخونه و الگوریتم‌های آمادست و بنابراین هیچ چالشی برای Bypass کردنشون ندارین. هدف این دوره در واقع حل چالش‌های اون 20% باقی مونده‎‌ست.اگه در زمینه باگ‌بانتی و Pentest اپلیکیشن‌های خارجی کار می‎‌کنین، نسبتا اوضاع بهتری دارن. مثلا در زمینه اپلیکیشن‌های بانکی و پرداخت بین المللی اکثرشون در کنار برنامه باگ‌بانتی همیشگی، از راهکار‌های تجاری زیادی مثل nowsecure ،zimperium ،guardsquare و... استفاده کردن که باعث شده Reverse اشون با چالش‌های خیلی زیادی همراه باشه. این پست رو دیگه بیشتر از این طولانی نکنم... 😅سورس‌کد اسکریپتی که  کار کردیم:https://github.com/EmadAbedini/Android-Pentest-Challengesاگه به امنیت علاقه دارین، این دو پست هم احتمالا براتون جالب باشه: https://virgool.io/@emad_abedini/%D8%A2%D8%B3%DB%8C%D8%A8-%D9%BE%D8%B0%DB%8C%D8%B1%DB%8C-%D8%A7%D9%BE%D9%84%DB%8C%DA%A9%DB%8C%D8%B4%D9%86-%DB%8C%DA%A9%DB%8C-%D8%A7%D8%B2-%D8%A7%D8%B3%D8%AA%D8%A7%D8%B1%D8%AA-%D8%A2%D9%BE-%D9%87%D8%A7%DB%8C-%D9%85%D9%88%D9%81%D9%82-%D8%AF%D8%A7%D8%AE%D9%84%DB%8C-qxvwytrnhrrv  https://virgool.io/@emad_abedini/%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-%D8%B3%D9%88%D8%B1%D8%B3-%DA%A9%D8%AF-%D8%A7%D9%BE%D9%84%DB%8C%DA%A9%DB%8C%D8%B4%D9%86-%D9%87%D8%A7%DB%8C-%D8%AC%D8%B9%D9%84%DB%8C-%D8%AB%D9%86%D8%A7-khkqn4dlr9fh امیدوارم این پست براتون مفید بوده باشه. سعی می‌کنم بخش‌های بعدی زودتری آماده بشه.راستی اگه دوست داشتید میتونید از طریق لینکدین باهام در ارتباط باشید. https://www.linkedin.com/in/emad-abedini/  https://www.linkedin.com/in/emad-abedini/ شاد و موفق باشید... ❤️</description>
                <category>عماد عابدینی</category>
                <author>عماد عابدینی</author>
                <pubDate>Thu, 05 Dec 2024 22:54:33 +0330</pubDate>
            </item>
                    <item>
                <title>روش Intercept ترافیک در اپلیکیشن‌های Flutter ی</title>
                <link>https://virgool.io/@emad_abedini/%D8%B1%D9%88%D8%B4-intercept-%D8%AA%D8%B1%D8%A7%D9%81%DB%8C%DA%A9-%D8%AF%D8%B1-%D8%A7%D9%BE%D9%84%DB%8C%DA%A9%DB%8C%D8%B4%D9%86-%D9%87%D8%A7%DB%8C-flutter-%DB%8C-b8i99jgcdmyh</link>
                <description>یکی از اصلی‌ترین بخش‌ها در کرک، مهندسی معکوس و تست نفوذ اپلیکیشن‌های اندرویدی Intercept کردن ترافیکشون میشه. مراحل کلی به این صورت میشه:نصب اپلیکیشن سر Device فیزیکی و یا Emulator سِت کردن Proxy سر دیوایس/شبیه سازاضافه کردن CA مربوط به Proxy Server به عنوان CA مورد تایید گرفتن ترافیک با ابزار‌هایی مثل Burp Suite و یا Zap حالا این وسط خیلی وقتا نیازه الگوریتم‌های حفاظتی اپلیکیشن مثل SSL Pinning، Root Detection و Emulator Detection رو هم Bypass کنیم.اگه با روش‌های بالا بردن امنیت اپلیکیشن‌های اندرویدی آشنا نیستین، این پست ویرگول میتونه مفید باشه.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ولی...داستان برای اپلیکیشن‌هایی که با 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 دسترسی داریم، برای حل مشکل Proxy از دو روش می‌تونیم استفاده کنیم:استفاده از اپلیکیشن ProxyDroidاستفاده از iptablesروش اول اینه که بیایم ProxyDroid رو نصب و اجرا کنیم. بعدش میایم IP و Port پراکسی سرور رو در بخش Proxy Setting وارد می‌کنیم. بعد از بخش individual proxy، اپلیکیشنی که می‌خوایم ترافیکش از Proxy عبور کنه رو انتخاب می‌کنیم. در مرحله آخر Proxy Switch رو فعال می‌کنیم:تو این مرحله نیاز به Transparent Proxying داریم، تو Burp Suite اسمش رو گذاشته 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 بازش کردیمحالا مقدار String زیر رو از بخش Search و بعد For Strings فیلتر می‌کنیم: (باید صبر کنین Analyze کارش تموم شده باشه. نسبت به سیستمتون ممکنه زمان بگیره)ssl_x509فیلتر کردن ssl_x509 از Stringهاحالا اگه دو بار روش کلیک کنیم، فانکشن‌هایی که این مقدار (ssl_x509) داخلشون هست رو می‌تونیم ببینیم:همونطور که مشخصه مقداری که دنبالش هستیم یه جایی تو محدوده 0x300000 هست ولی ما نیاز داریم دقیقش رو پیدا کنیم. اینجا 5 تا فانکشن داریم که این مقدار داخلشون استفاده شده و روی هر کدوم که کلیک کنیم می‌تونیم محتواش رو ببینیم. ولی ما باید فانکشن اصلی‌ای که وظیفه SSL Pinning رو بر عهده داره رو پیدا کنیم، چجوری؟ خیلی راحت روش اصلی اینه که بیایم هر فانکشن رو جداگونه بررسی کنیم ببینیم کدوم فانکشن اصلیه که زمان زیادی می‌گیره. برای این که کار راحت‌تر و سریع‌تر شه می‌تونیم از فانکشن‌های نمونه زیر استفاده کنیم:فانکشن مربوط به SSL Pinning در معماری 32 بیت:32 بیتفانکشن مربوط به SSL Pinning در معماری 64 بیت:64 بیت در واقع تنها کاری که لازمه انجام بدیم اینه که از فانکشن‌هایی که پیدا شده (همون 5 تا) دنبال فانکشنی باشیم که شبیه به این دو نمونه بالاست. ( با یه نگاه کلی می‌تونین پیداش کنین).الان مثلا تو این مثال ما فانکشن 0x3e236c میشه همون فانکشنی که دنبالش هستیم: به معماری Device/Emulator که اپلیکیشن رو سرش نصب کردین حتما دقت کنید.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 بودن داره رو پیدا کنیم؟ من از این دو روش استفاده می‌کنم و اکثر وقتا جواب میده: (اگه با این دو روش پیدا نشد باید یکم وقت بذاریم و با گشتن تو سورس کد پیداش کنیم)جستجو مقدار استرینگ &quot;su&quot; در سورس کد استخراجیدیکامپایل با Apktool و جستجو مقدار استرینگ &quot;root&quot; در بخش 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 امیدوارم این پست براتون مفید بوده باشه.شاد و موفق باشین ❤️</description>
                <category>عماد عابدینی</category>
                <author>عماد عابدینی</author>
                <pubDate>Tue, 21 Nov 2023 22:04:57 +0330</pubDate>
            </item>
                    <item>
                <title>چجوری خیلی وقتا PocketBase کارمون رو راحت میکنه؟</title>
                <link>https://virgool.io/@emad_abedini/%DA%86%D8%AC%D9%88%D8%B1%DB%8C-%D8%AE%DB%8C%D9%84%DB%8C-%D9%88%D9%82%D8%AA%D8%A7-pocketbase-%DA%A9%D8%A7%D8%B1%D9%85%D9%88%D9%86-%D8%B1%D9%88-%D8%B1%D8%A7%D8%AD%D8%AA-%D9%85%DB%8C%DA%A9%D9%86%D9%87-n3p1icasxm9i</link>
                <description>فرض کنید میخوایم برای Backend پروژه برنامه‌ریزی کنیم که به چه صورت انجام بشه. خب معمولا اینطوریه که:بیایم Backend رو خودمون بزنیم (کاملا از صفر یا به کمک فریمورک‌ها)بیایم Backend رو بسپاریم به یک شخص/تیم دیگهبیایم از Backend as a service (BaaS)ها (فعلا بهش بگیم Backendهای آماده!) استفاده کنیم.AI Imageهر کدوم از این حالت‌ها چالش‌های خاص مخصوص به خودشون رو دارن.به فرض اگه قرار باشه Backend رو خودمون بزنیم، می‌تونیم جوری پیاده سازیش کنیم که از بهینه بودن و امنیت‌ش خیالمون راحت باشه؟اگه بسپاریمش دست یک شخص/تیم دیگه، تا زمانی که پروژه بالا باشه پشتیبانی‌ش رو به خوبی انجام میده؟اگه بخوایم از BaaSها استفاده کنیم، از کدوم استفاده کنیم؟ اصلا BaaSها جوابگوی کار ما هستن؟اینکه کدوم حالت رو انتخاب کنیم و کدوم بهتره، طبیعتا بستگی به فاکتور‌های زیادی از پروژه داره، ولی خب از اونجایی که هدف از این پست آشنا شدن با یکی از BaaSها به اسم PocketBaseه، در ادامه می‌خوایم در مورد خوبی‌های استفاده از BasSها بدونیم.بریم سراغ BaaSهاهمونطور که از اسمش (Backend as a service) مشخصه، BaaS خیلی ساده که بخوایم بگیم یعنی جای اینکه Backend رو خودمون بزنیم، از ارائه دهنده‌های سرویس Backend استفاده کنیم! شاید سوال پیش بیاد که مگه میشه!؟ بله میشه. ولی قطعا نه همیشه. اگه برای پروژتون به یک API نیاز دارین که عمده اصلی کارش فرایند CRUD هست، حالا این وسط احراز هویت و کنترل دسترسی نسبتا پایه ای رو هم براش در نظر دارین، به احتمال خیلی زیاد استفاده از BaaSها انتخاب مناسبی برای شماست.اما بعضی وقتا هست که Backend قرار نیست فقط یه CRUD باشه، یه وقتی هست سمت سرور قراره پردازش AI یا به فرض Machine Learning یا اصلا یه وقتی هست که ما بر حسب پروژمون نیاز به دیتابیس خاصی مثل Neo4j داریم که خب طبیعیتا توی یه همچین حالت‌هایی نمیتونیم از BaaS‌ها استفاده کنیم. (البته نه که کلا نتونیم، میشه بخشی از کار رو با BaaSها انجام بدیم و بخش دیگه رو خودمون پیاده‌سازی کنیم، ولی در کل امکاناتی که BaaSها دارن یکسری امکانات عمومیه و نباید از یه BaaS انتظار داشته باشیم هر چی تو ذهنمون هست رو داشته باشه!)source: https://blog.back4app.com/mbaas-comparison/  حالا اگه یه سرچ ساده انجام بدیم، می‌بینیم که کلی ارائه دهنده‌ی BaaS داریم، چند تا از محبوب‌هاشون:AWS AmplifyFirebaseNetlifyAppwritePocketBaseSupabase...حالا ما تو این پست می‌خوایم با یکی از این BaaSها به اسم PocketBase آشنا بشیم، اینکه چجوری باید ازش استفاده کرد، چه امکاناتی داره و چجوری می‌تونه به عنوان Backend پروژه ازش استفاده بشه که در نهایت باعث شه هم کلی تو وقتمون صرفه‌جویی بشه و هم اینکه از نتیجه کار هم راضی باشیم...راستی اینم بگم که من از BaaSها با Firebase (GCP), Amplify (AWS) و Supabase کار کردم، ولی از کار کردن با PocketBase حس خیلی بهتری داشتم و از اون جایی که PocketBase هنوز اونقدری محبوب نشده، گفتم این پست رو بذارم شاید برای کسی مفید بود. ?بعضی از امکاناتی که PocketBase داره:کلا یدونه فایله! یعنی چی؟ یعنی برای راه‌اندازی PocketBase ما کلا با یدونه فایل سرو کار داریم، همین یدونه فایل رو اجرا می‌کنیم و تموم... ?نحوه نصب و استفاده ازش به شدت راحته.امکان نصب و راه‌اندازی روی سرور شخصی خودمون داره.امکان تعریف احراز هویت (authentication) و کنترل دسترسی (authorization) خیلی خوبی داره.امکان پیاده سازی احراز هویت به صورت دستی (نام کاربری، کلمه عبور) و یا OAuth2 (اکثر سرویس دهنده‌ها رو هم خودش از قبل داره، مثل Google, Facebook, Github و...)دیتابیس به صورت real-timeایجاد روابط بین کالکشن‌هابرای Dart و JavaScript هم SDK داره و نیازی نیست مثلا بیایم از axios استفاده کنیم.کاملا رایگانهو کلی امکانات دیگه...source: https://productcoalition.com/what-is-baas-backend-as-a-service-9c9a8ed12550 یه مثال از نحوه کار با PocketBase ببینیم...پیاده سازی User Managementبیایم فرض کنیم می‌خوایم User Management رو با PocketBase پیاده‌سازی کنیم.(این مثالی که می‌زنیم یه مثال خیلی سادست که فقط با نحوه کار با PocketBase آشنا بشیم.) خب برای user management تو ساده‌ترین حالت هر کاربر یک username و یک password داره (حالا برای اینکه یکم مثالمون قشنگ‌تر بشه یه فیلد دیگه هم در نظر می‌گیریم که کاربر admin هست یا نه، اسمشم میذاریم is_admin)پس بریم تو پنل PocketBase و یک کالکشن به اسم users ایجاد کنیم:ایجاد کالکشن users به همراه فیلدهای username, password و is_adminانتخاب type هر فیلد هم به سادگی انجام میشه:تعیین type برای هر فیلد کالکشنی که ایجاد کردیم میشه به این صورت: (یه رکورد هم بهش دستی اضافه شده اینجا)کالکشن usersخب کار تمومه... الان CRUD روی این کالکشن آماده استفادست.برای نمونه Post و Deleteش به این صورت میشه :Post Deleteکلی هم حالت‌های مختلف برای user management در اختیارمون میذاره:user management  دسترسی به هر Route رو هم می‌تونین خیلی راحت محدود کنین:تعیین سطح دسترسی برای هر Routeاز این بخش هم می‌تونین log هاتون رو چک کنین:لاگ کاربران معمولی و مدیر و کلی امکانات دیگه که باید خودتون باهاش کار کنین...چجوری روی سیستم شخصی (Local) نصبش کنیم؟اگه بخوایم از PocketBase برای فرایند تست و در کل development استفاده کنیم، میتونیم local بیاریمش بالا که تو این حالت نصبش خیلی سادست و نیاز به توضیحی نداره، کافیه از اینجا نسبت به سیستم عامل دانلودش کنیم و تموم.چجوری Online بهش دسترسی داشته باشیم؟حالا یه موقع هست که می‌خوایم برای production ازش استفاده کنیم، یا در کل به هر دلیل می‌خوایم آنلاین بهش دسترسی داشته باشیم که این خودش میشه دو حالت:- میایم یه سرور اجاره می‌کنیم، PocketBase رو روش نصب می‌کنیم و ازش استفاده می‌کنیم.- حالت دوم اینکه حالو حوصله Server رو نداریم (اینطوری نباشیم ?) ، تو این حالت می‌تونیم از سرویس دهنده‌هایی مثل آروان، لیارا و چابکان استفاده کنیم که کل فرایند رو انجام میدن.(البته آروان فعلا تو لیست Appهای پیشفرض ندارش ولی میتونین داکرش رو خیلی راحت تو پنل کانتینر ابری‌ش اضافه کنید)ایجاد PocketBase در پنل چابکانچجوری روی Server نصبش کنیم و به بیرون Exposeش کنیم؟همونطور که صحبتش شد، یه راه استفاده از PocketBase اینه که بیایم رو سرور خودمون بیاریمش بالا. قبلش بگم که مستندات کامل Deploy کردنش رو از اینجا میتونین مطالعه کنین و از اینجا هم میتونین با نسخه Demoش کار کنین.خب از اونجایی که معمولا سرور رو لینوکسی می‌گیریم، توضیحات رو با لینوکس میریم جلو:اول از اینجا نسخه لینوکسیش رو دانلود می‌کنیم، به سرور انتقالش میدیم و unzipش می‌کنیم.حالا کامند زیر رو اجرا می‌کنیم و تموم! همین./pocketbase serveبعد از اجرا شدن، بهمون 2 تا مسیر میده:الان فقط از داخل سرور بهش دسترسی داریم، پس باید به بیرون هم exposeش کنیم که بتونیم خارج از سرور هم بهش دسترسی داشته باشیم.اینجا هم PocketBase کارو برامون راحت کرده و فقط کافیه به جای کامند بالا از این یکی استفاده کنیم:./pocketbase serve --http= [SERVER_IP/DOMAIN : PORT]الان دیگه می‌تونیم با IP سرور و از اینترنت هم بهش دسترسی داشته باشیم، ولی مشکلی که داره اگه ارتباطمون با سرور قطع شه (مثلا ssh زدیم و ببندیمش) و یا سرور ریستارت شه، دیگه بهش (PocketBase) دسترسی نداریم، برای حل این مشکل می‌تونیم یه سرویس اضافه کنیم، به این صورت:تو مسیر زیر یک فایل ایجاد می‌کنیم:/lib/systemd/system/pocketbase.serviceو محتواش رو به صورت زیر قرار میدیم:تعریف سرویسو بعد میایم سرویسی که تعریف کردیم رو start می‌کنیم:systemctl enable pocketbase.serviceو آخرین مرحله اینکه PocketBase رو هم start می‌کنیم:systemctl start pocketbaseبا این سرویسی که تعریف کردیم با بسته شدن ssh و یا ریستارت شدن سرور، PocketBase خودش دوباره start میشه.AI Image  تو این پست سعی کردم به شما یک دید کلی از BaaSها و PocketBase بدم. اینکه خیلی وقتا به جای صرف زمان و هزینه برای توسعه Backend میتونیم از BaaSهایی مثل PocketBase استفاده کنیم. راستی:در حال حاضر آخرین نسخه PocketBase ، نسخه 0.19.3 هست و بهتره تا قبل منتشر شدن نسخه 1.0.0 برای پروژه‌های تجاری ازش استفاده نکنید. (کلا برای پروژه‌های تجاری سطح متوسط به بالا، استفاده از BaaS رو پیشنهاد نمی‌کنم.)همونطور که بالاتر هم توضیح دادم، استفاده از BaaSها خیلی وقتا انتخاب درستی نیست. (بستگی به پروژه داره)سرویس‌هایی مشابه با PocketBase زیاد هستن، این PocketBase هم به قول یکی از دوستان خیلی چیز WOW ای نیست، ولی نسبت به بقیه BaaSها احتمالا شما هم حس بهتری ازش می‌گیرین.امیدوارم از این پست خوشتون اومده باشه و براتون مفید بوده باشه. اگه دوست داشتید میتونید از طریق لینکدین باهام در ارتباط باشید.?موفق باشید...</description>
                <category>عماد عابدینی</category>
                <author>عماد عابدینی</author>
                <pubDate>Thu, 09 Nov 2023 19:03:14 +0330</pubDate>
            </item>
                    <item>
                <title>آسیب‌پذیری اپلیکیشن یکی از استارت‌آپ‌ های موفق داخلی</title>
                <link>https://virgool.io/@emad_abedini/%D8%A2%D8%B3%DB%8C%D8%A8-%D9%BE%D8%B0%DB%8C%D8%B1%DB%8C-%D8%A7%D9%BE%D9%84%DB%8C%DA%A9%DB%8C%D8%B4%D9%86-%DB%8C%DA%A9%DB%8C-%D8%A7%D8%B2-%D8%A7%D8%B3%D8%AA%D8%A7%D8%B1%D8%AA-%D8%A2%D9%BE-%D9%87%D8%A7%DB%8C-%D9%85%D9%88%D9%81%D9%82-%D8%AF%D8%A7%D8%AE%D9%84%DB%8C-qxvwytrnhrrv</link>
                <description>(این آسیب‌پذیری مربوط میشه به حدود 3 سال پیش، همون موقع گزارش شد و بعد از 2 هفته به طور کامل برطرف شد)اجازه انتشار اسم شرکت داده نشد. بنابراین تمام تصاویری که به عنوان اسکرین‌شات از اپلیکیشن در این گزارش قرار داده شده تزیینی هستن و برای این گزارش آماده شدن (اسکرین شات نیستن)این آسیب‌پذیری باعث امکان دسترسی کامل به دیتابیس + امکان تغییر رکوردهای دیتابیس + چندین مورد دیگه شد که در ادامه روش کار رو توضیح دادم...داستان از اونجایی شروع شد که داشتم تو وب سایت این شرکت یه موضوعی رو پیگیری می‌کردم که اتفاقی متوجه شدم تو یکی از صفحات زمانی که صفحه رفرش میشه یک button هست که سریع حذف میشه و دیگه امکان کلیک رو به کاربر نمیده. (وب سایت SPA بود و با React نوشته شده بود)معمولا این حالت زمانی پیش میاد که Componentی که قراره بر اساس سطح دسترسی به کاربر نشون داده بشه قبل از دریافت و بررسی Response مرتبط با سطح دسترسی، Render میشه و بعد که Response اومد اگه سطح دسترسی لازم رو نداشت تو Render مجدد دیگه به کاربر نشون داده نمیشه.بگذریم... پس اومدم request رو نگه داشتم که بتونم اون button رو بررسی کنم. ( یک راه ساده دیگه‌ش هم اینه که بیای از Network throttling سرعت رو بیاری پایین)کاری که این button انجام میداد هدایت کاربر به صفحه‌ای بود که فقط کاربرایی با سطح دسترسی مدیر باید بهش دسترسی داشته باشن (نه کاربرای معمولی)البته بیشتر component های اون صفحه لود نمیشد ولی لینک دانلود یک فایل APK داخل فوتر اون صفحه بود. (این شرکت خودش یک اپلیکیشن داره که به صورت عمومی از مارکت‎‌ها قابل دانلوده ولی این اپلیکیشن اون اپلیکیشن نبود)از سر کنجکاوی دانلودش کردم ببینم چیه، بعد از نصب و اجرا کردنش همون اول کار شماره موبایل میخواست، شمارمو وارد کردم ولی گفت شماره وجود ندارد و گزینه‌ای هم برای ثبت نام نداشت، پس میشد احتمال داد که شماره‌هایی مجاز به استفاده از این اپلیکیشن هستن که از قبل رجیستر شده باشن، این موضوع باعث شد بیشتر کنجکاو شم در موردش...اول از همه اومدم کل String هایی که داشت رو بررسی کردم تا شاید اینجوری بتونم متوجه بشم این اپلیکیشن قراره چه کاری انجام بده.برای استخراج String ها ابزار خیلی زیاد هست، ولی سریع ترین روش به نظرم اینه:aapt d --values strings [APK_FILE]بعد از بررسی Stringها دیگه مطمئن شدم این اپلیکیشن فقط مختص کاربر با سطح دسترسی مدیره، نه کاربرای معمولی. پس میشد احتمال داد که بشه کارای زیادی باهاش انجام داد...خلاصه که بعد از دیدن Stringهایی که داشت دیدم ارزشش رو داره سرش وقت بذارم...اولین روشی که به نظرم رسید این بود که ترافیک اینترنتش رو بررسی کنم تا شاید بشه با یکم تغییر روی response، لاگین رو دور زد (خیلی وقتا همین روش ساده هم جواب میده)، رفتم سراغ گرفتن ترافیکش...از SSL Pinning استفاده کرده بود:با این اسکریپت راحت Bypass شد:https://codeshare.frida.re/@pcipolloni/unsal-android-ssl-pinning-bypass-with-fridaمشکل بعدی این بود که ترافیک به صورت رمز شده ارسال/دریافت میشد: (ترافیک اون یکی اپلیکیشن این شرکت که به صورت عمومی از مارکت قابل دانلود بود رو هم چک کردم، SSL Pinning داشت ولی ترافیک Encrypt نمیشد)البته Encrypt کردن ترافیک یه چیز عادیه و خیلی از اپلیکیشن و بازی‌ها از این روش استفاده میکنن. (منظور اینکه Encrypt کردن ترافیک کار خیلی خاصی نیست)اینجور وقتا یه روش اینه که نوع الگوریتم رو در بیاری که بتونی ترافیک رو Encrypt/Decrypt کنی، اومدم فایلشو Decompile کردم و توی سورس کدش دنبال همچین چیزایی گشتم:Encrypt , Decrypt , AES, RSA, Encode, Decode &amp;...مشکلی که وجود داشت سورس کدش خیلی زیاد بود و کلی زمان میگرفت بخوام نتایجی که پیدا شده رو بررسی کنم:روش بعدی که به نظرم رسید این بود که به جای لاگین، بخش بعدیش که احتمالا میشد وارد کردن OTP رو با adb بیارم بالا، یه نگاهی به Manifest انداختم ولی هیچ activity ای که اسمش به وارد کردن OTP بخوره پیدا نکردم... (البته اگه با Jetpack Compose نوشته میشد این روش کمکی نمیکرد)پس دوباره برگشتم سر activity مرتبط با لاگین (اسمش دقیقا Login بود)، سورسش رو چک کردم، به نظر می‌رسید وارد کردن OTP هم توی همین activity لاگین انجام میشه (جفتشون توی یک layout بودن):فایل layout مربوط به بخش لاگین رو چک کردم و دیدم بله، جفتشون رو توی یک layout در نظر گرفتن:پس دیگه نمیشد با adb بیارمش بالا (چون با login توی یک activity بودن و برای اینکه از وارد کردن شماره به وارد کردن OTP تغییر کنه نیاز بود response ای که از سرور میاد بگه شماره وجود داره)ولی بازم یه راه ساده دیگه وجود داشت، اینکه به جای بالا اوردن بخش وارد کردن OTP، مستقیم بخش اصلی رو بیارم بالا...دوباره Manifest رو چک کردم دیدم یه activity داریم به اسم main و خب بدون بررسی سورس کد هم انتظار میرفت صفحه اصلی‌ای که قراره کاربر بعد از وارد کردن OTP واردش بشه همین main باشه...اومدم main رو با adb بیارم بالا ولی crash کرد، ساده ترین دلیل که احتمالشم زیاد بود این بود که موقعی که OTP به صورت صحیح وارد میشه، یکسری مقادیر از response دریافت و به main ارسال میشن و چون الان بدون ارسال اون مقادیر میخواستم بیارمش بالا باعث crash شده، پس نیاز بود دوباره یه نگاهی به سورس کد بندازم:همونطور که احتمالش رو میدادم، main نیاز به یکسری مقادیر داشت که چون اینارو نداشته باعث crash شدنش شده...در واقع main میاد 3 تا مقدار رو از sharedPreference می گیره، پس احتمالا موقعی که OTP صحیح وارد میشه، اینارو از response ای که میاد میگیره و میریزه تو sharedPreferenceباز برای اطمینان گشتم دنبال این بخش توی سورس کد، که میشد اینجا:حالا میشد بیام همین مقادیری که نیاز داره رو دستی توی sharedPreferense اضافه کنم و یا موقعی که دارم main رو با adb میارم بالا، اینارم بهش بدم...ولی فقط نوعشون (Typeشون) مشخص بود، اینکه طولشون چقدره و با چه فرمتی هستن مشخص نبود...هر دو روش رو با حالت های مختلفی که به نظرم رسید تست کردم ولی نشد و بازم crash میکرد...دو تا راه داشتم، یا باید میومدم الگوریتمی که برای Encrypt/Decrypt ترافیک ارسالی/دریافتی استفاده کرده رو در بیارم تا بتونم Encrypt/Decryptش کنم، یا اینکه بیام سمت سرور رو چک کنم ببینم میشه یه شماره رو رجیستر کنم یا حداقل یه شماره رجیستر شده پیدا کنم یا نه...یه نگاهی به APIش انداختم، خیلی راحت میشد StackTrace گرفت ازش: (بهش Improper Error Handling میگن)سمت سرور با Java بود و ازونجایی که اون زمان Log4Shell تازه Public شده بود (فکر کنم هنوز یک هفته هم نشده بود)، این احتمال وجود داشت که نسبت بهش آسیب پذیر باشه، از طرفی هم میشد به آپدیت نبودن Weblogic هم امیدوار بود...ولی ازونجایی که سرور فقط برای سرویس دهی به IP های داخلی (ایران) محدود شده بود اگه از هر کدوم از روش‌هایی که در دسترس داشتم استفاده می کردم بازم ریسک داشت (این شرکت اون زمان برنامه‌ای برای بانتی نداشت، در واقع اجازه اینکه بخوای روش تست امنیت بزنی رو نداشتی)، از طرفی ممکن بود WAF متوجه بشه و policy ها رو بیشتر کنن و خب اینجوری سمت اپلیکیشن هم کار سخت میشد، دیگه خلاصه دیدم فعلا وقت سمت سرور نیست و به همین خاطر دوباره برگشتم سر اپلیکیشن...راه بعدی ای که به نظرم رسید این بود که یه OSINT روی کارکنان اون شرکت بزنم ببینم میتونم شماره موبایلی ازشون پیدا کنم یا نه، کلا 3 تا شماره پیدا کردم ولی بازم هر 3 تا رو میگفت این شماره وجود نداره...!احتمالی که وجود داشت این بود که فقط شماره رجیستر نمیشه، شاید شماره با IMEI کنار هم رجیستر میشن یا شایدم واقعا اون 3 شماره رجیستر نشده بودن، خلاصه اینکه این روش هم جواب نداد...دیگه وقتش بود بیام الگوریتم رمزنگاریش رو پیدا کنم،  یا باید میومدم سورس رو چک میکردم که این خیلی وقت گیر بود، یا اینکه سعی میکردم با frida و hook کردن، ترافیک رو Decrypt کنم، در این خصوص مطلب و آموزش خوب خیلی زیاد هست، مثلا میتونین یه نگاهی به این بندازین:https://11x256.github.io/Frida-hooking-android-part-5نتیجه کار به این صورت شد:درخواست ارسالی:درخواست ارسالی به صورت Encrypt شده:پاسخ دریافتی بعد از Decrypt :الگوریتم و کلید استفاده شده:خب حالا که الگوریتم مشخص شده بود، باید یه اسکریپت می نوشتم که بتونم باهاش Encrypt/Decrypt کنم...دو تا راه داشتم، یا اینکه یه Extender برای Burpsuite بنویسم و یا اینکه یه اسکریپت جدا برای خودم بنویسم...من معمولا عادت دارم موقع گزارش آسیب پذیری، جدا از فایل گزارش (متن، تصویر و ویدیو)، اگه امکانش باشه برای اون آسیب پذیری یه برنامه/اسکریپت ساده هم می نویسم که خود اون نفر/شرکت/سازمان بتونه با یه کلیک به همون نتیجه ای که رسیدم برسه...، شاید به نظرتون وقت گذاشتن الکی باشه ولی به نظرم باعث میشه هم گزارش قشنگ‌تر در بیاد و هم اینکه همیشه بازخورد خوبی داشته برام این کار...خلاصه که به همین خاطر تصمیم گرفتم به جای Extender ، اسکریپت خودم رو بنویسم که نخوام دو بار وقت بذارم. (برای نوشتنش میشد از Crypto.js استفاده کرد و کار وقت گیری نبود اصلا)https://github.com/brix/crypto-jsولی یکم که سرچ کردم دیدم از قبل Encryptor/Decryptor این الگوریتم نوشته شده (یه Gist بود)البته یکسری بخش ها از کدش رو باید حذف میکردی تا درست کار کنه!!تا اینجای کار همه چیز خوب پیش رفته بود و میشد خیلی راحت دیتای ارسالی/دریافتی رو Encrypt/Decrypt کرد، ولی بازم یه مشکلی وجود داشت، بعضی response ها رو نمیتونست Decrypt کنه!بعد از بررسی سورس کد (بخش Encrypt دیتای ارسالی توی اپلیکیشن) متوجه شدم که بعد از Encrypt به صورت random به آخر بعضی از رشته ها میاد یه \r\n اضافه میکنه و موقع Decrypt هم اگه آخرش اینو داشت اول حذفش میکنه!! همین کارو توی اسکریپت بالا انجام دادم و مشکل حل شد و دیگه تموم response ها Decrypt میشدن...خب حالا باید میرفتم با یه شماره لاگین کنم ببینم چه responseی میاد تا شاید بشه با تغییر response به مرحله وارد کردن OTP رسید ( نمیشد مستقیم برم صفحه اصلی، چون همونطور که توضیح دادم main به responseی که بعد از صحیح وارد کردن OTP میومد نیاز داشت)، بعد از وارد کردن شماره، responseی که میومد به این صورت بود:میشد احتمال داد که برای شماره های رجیستر شده resultCode مقدارش فرق میکنه، چون بعید بود بیان برای این موضوع از msg استفاده کرده باشن...پس باید میرفتم ببینم resultCode اگه چند باشه میره صفحه بعد، باز یه نگاهی به سورس انداختم:همونطور که مشخصه اگه برابر با 1 باشه میره روی حالت اینکه OTP رو وارد کن، پس اومدم 200 رو به 1 تغییر دادم و بخش وارد کردن OTP اومد بالا:یه مقدار تستی برای OTP وارد کردم، responseی که میداد این بود:دوباره نیاز بود سورس رو بررسی کنم، چون همونطور که بالاتر سورس کد رو دیدیم بعد از وارد کردن OTP صحیح، یکسری مقادیر از response دریافت و توی sharedPreference ریخته میشه و main میاد ازون مقادیر استفاده میکنه...نتیجه ای که بعد از بررسی سورس کد این بخش گرفتم این بود که اگه OTP صحیح باشه یه object به اسم response هم بهش اضافه میشه که همون 3 مقداری که قراره توی sharedPreference ریخته بشن رو از این قسمت برمیداره...پس اومدم resultCode رو دوباره 1 کردم و به response یه object به اسم response که اون 3 مقدار رو هم داخلش داشت اضافه کردم، ازونجایی که نمیدونستم طول و فرمت اون 3 مقدار به چه صورتیه باید حالت های مختلف رو تست میکردم، برعکس دفعه قبلی که نشد ولی این بار بعد از چندین بار تست کردن حالت های مختلف بالاخره صفحه اصلی اپلیکیشن اومد بالا...بازم یه مشکلی بود، روی هر بخشی کلیک میکردی crash میکرد بدون اینکه هیچ data ای ارسال/دریافت بشه!دوباره سورس رو بررسی کردم، مشکلی که وجود داشت این بود که بعد از اولین کلیک توی main بازم یکسری مقادیر توی sharedPreference ریخته میشدن، 2 تا ازین مقادیر با اون 3تایی که خودم بهش داده بودم ارتباط داشت و این احتمال وجود داشت که طول و یا فرمتی که برای اون 3 مقدار در نظر گرفته بودم درست نبوده و این وسط یه مشکلی پیش اومده...بعد از 5-6 بار تغییر دستی sharedPreference بالاخره مشکل حل شدتو این مرحله دیگه میتونستم راحت وارد بخش اصلی اپلیکیشن بشم. تو صفحه اصلی میشد تغییرات نسبتا زیادی رو روی رکوردهای هر کاربر ایجاد کرد.بعد از انجام چند تا تست به این نتیجه رسیدم که خب WAF که به خاطر Encrypt شدن دیتای ارسالی/دریافتی کلا چیزی رو نمی گیره و مهم تر اینکه ورودی ها کلا Sanitize نمیشدن! (احتمالا خیالشون از رمزنگاریشون راحت بوده دیگه حوصله Sanitize نداشتن)  و از طرفی هم هیچ محدودیتی روی تعداد request ارسالی وجود نداشت...!!پس همه چیز آماده بود برای گرفتن کل دیتابیس (که البته فقط 200 تا رکورد گرفتم، هدف گرفتن کلش نبود)برای گرفتن شماره های از قبل رجیستر شده هم یه اسکریپت پایتونی نوشتم که هم میشد بهش لیست شماره بدی و یا اینکه بگی random شماره تست کنه و یا یه بخشی از شماره رو بهش بدی و یکی یکی اضافه کنه بره جلو...یه delay هم براش در نظر گرفتم که اگه یه موقع نیاز شد ازش استفاده کنم (که نیاز نشد)نتیجه کار:خب این شد از لیست کل شماره های رجیستر شده... (که البته اینم فقط 20 تاشو گرفتم)اینجا به نظرم رسید یه اسکریپت بنویسم و کل ورودی ها رو بررسی کنم...نتیجه کار شد یه اسکریپت پایتونی که یه لیست payload بهش میدادی و روی کل ورودی ها تست میکرد...(برای payload من معمولا از PayloadsAllTheThings استفاده میکنم + payloadهایی که از اینور اونور جمع میکنم)https://github.com/swisskyrepo/PayloadsAllTheThingsچند تا آسیب پذیری‌ دیگه هم داشت که برای اینا دیگه اسکریپت ننوشتم و فقط توی ویدیو ای که همراه با گزارش آسیب‌پذیری براشون ارسال کردم اینا رو هم توضیح دادم: (هر جور خواستم ویدیوش رو Edit کنم که بشه داخل این گزارش هم اوردش نشد و مشخص میشد کدوم شرکته)یکی از Route ها Mass Assignment داشت، میشد پارامترهایی که حتی مدیر هم اجازه تغییرشون رو نداره رو تغییر داد. اونجایی که میخواست OTP رو ارسال کنه، میشد بخشی از متن sms رو تغییر داد. (اینم خیلی ساده بود، 11 کاراکتر مرتبط با SMS Retriever API داخل Request بود و اگه تغییرش میدادی با تغییر ارسال میشد.)هیچ محدودیتی روی تعداد sms ی که قرار بود OTP رو ارسال کنه وجود نداشت! (اسکرین شات زیر مربوط به Turbo Intruder، یکی از Extender های BurpSuiteه)اینجا دیدم سمت اپلیکیشن دیگه کافیه و بهتره یکمم سر وب سرور کار کنم، چون از طرفی از سرور Log4Shell داشتم و همین تا حد خوبی کافی بود برای شروع کار...سر فرصت writeup آسیب‌پذیری های سمت سرورش رو هم تو یه پست دیگه آماده میکنم.ممنون که وقت گذاشتین این گزارش رو خوندین و امیدوارم که براتون جالب بوده باشه... </description>
                <category>عماد عابدینی</category>
                <author>عماد عابدینی</author>
                <pubDate>Wed, 29 Mar 2023 23:44:07 +0330</pubDate>
            </item>
                    <item>
                <title>14 نکته برای بهبود امنیت در اپلیکیشن‌های اندرویدی</title>
                <link>https://virgool.io/fboard/14-%D9%86%DA%A9%D8%AA%D9%87-%D8%A8%D8%B1%D8%A7%DB%8C-%D8%A8%D9%87%D8%A8%D9%88%D8%AF-%D8%A7%D9%85%D9%86%DB%8C%D8%AA-%D8%AF%D8%B1-%D8%A7%D9%BE%D9%84%DB%8C%DA%A9%DB%8C%D8%B4%D9%86%E2%80%8C%D9%87%D8%A7%DB%8C-%D8%A7%D9%86%D8%AF%D8%B1%D9%88%DB%8C%D8%AF%DB%8C-vm8fzcs6zfh7</link>
                <description>چند وقت پیش یکی از دوستان قدیمیم که تو زمینه امنیت وب فعالیت داره بهم پیشنهاد داد در مورد روش‌های امن کردن اپلیکیشن‌های موبایلی ویدیو درست کنم، چون نظرش این بود که نسبت به امنیت وب توجه خیلی کمتری بهش شده...به نظرم پیشنهادش خوب اومد و اینجوری شد که تصمیم گرفتم سر فرصت چند تا از نکاتی که رعایت کردنشون باعث میشه امنیت اپلیکیشن‌های اندرویدی‌ چند پله بالاتر بره رو اینجا توضیح بدم که شاید بتونه برای کسی مفید باشه...اول ببینیم منظور از اینکه یک اپلیکیشن امن داشته باشیم یعنی چی؟این موضوع نسبت به هر اپلیکیشن میتونه معنی و روش پیاده‌سازی مختلفی داشته باشه، چند نمونه ازش رو مثال بزنم:اپلیکیشنی که کاربر میتونه در ازای پرداختی که انجام میده، سطح کاربریش رو ارتقا بده و یا مثلا محتوای خاصی رو دریافت کنه. امن بودن تو همچین اپلیکیشنی یعنی مطمئن بشیم پرداخت از هر نظر به طور صحیح انجام شده و کاربر نتونه بدون پرداخت و یا با پرداخت مبلغ کمتر جوری رفتار کنه که یک پرداخت موفق براش در نظر گرفته بشه...اپلیکیشنی که نیاز به لاگین داره ولی از طرفی امکان ثبت کاربر جدید رو نداره. مثلا فرض کنین یه سازمان اومده فقط مخصوص کارکنان خودش یه اپلیکیشن نوشته. امن بودن تو همچین اپلیکیشنی میتونه این باشه که مطمئن بشیم فقط کاربرای مجاز امکان استفاده از اپلیکیشن رو دارن...اپلیکیشنی که نیاز به لاگین داره، امکان ثبت کاربر جدید رو هم داره، ولی هر کاربر سطح دسترسی مخصوص به خودش رو داره. اینجا  امن نوشتن میتونه این باشه که کاربری با سطح دسترسی پایین‌تر نتونه به محتوای کاربرای سطح بالاتر دسترسی داشته باشه...اپلیکیشنی که هیچ محتوای خاصی نداره و یا اگرم داره تمام نکات مرتبط با امنیت رو رعایت کرده، ولی APIی داره که روی امنیتش کار نشده، تو همچین اپلیکیشنی هدف باید این باشه که APIش رو امن‌تر کنیم...مثال این مدلی زیاد میشه زد، خلاصه اینکه امن نوشتن اپلیکیشن‌های موبایلی نسبت به هر اپلیکیشن تعریف و روش پیاده سازی مخصوص به خودش رو داره، ولی یکسری نکات هستن که عمومی‌ان، که در ادامه چند تا از مهم‌ترین هاشون به همراه اینکه چرا باید انجام شن رو توضیح دادم...از SSL Pinning استفاده کنیددر خصوص اپلیکیشن‌هایی که به هر شکل با Web Server ارتباط دارن، از اولین نکاتی که باید رعایت کنیم اینه که حتما از SSL Pinning استفاده کنیم، چرا نیازه؟درسته که زمانی که ارتباط با وب سرور به صورت SSL باشه ترافیک به صورت انکریپت شده ارسال و دریافت میشه، ولی خیلی راحت میشه یک CA رو به عنوان Trust اضافه کرد و با یه Proxy کردن ساده به ترافیک دسترسی داشت...حالا SSL Pinning چکار میکنه؟ میاد میگه من کار ندارم چه CA هایی از نظر Browser و سیستم عامل Trust هستن، فقط در صورتی connection رو تایید میکنم که Certificate رو CA هایی که خودم مشخص کردم (Pin کردم) تایید کنن...اگه دوست دارین بیشتر در موردش بدونین: https://www.indusface.com/learning/what-is-ssl-pinning-a-quick-walk-through/  https://medium.com/@anuj.rai2489/ssl-pinning-254fa8ca2109 از Anti Emulator استفاده کنیدیکی دیگه از مهم ترین نکاتی که باید رعایت کنیم اینه که نباید اجازه اجرا شدن اپلیکیشن روی Emulator رو بدیم، حالا چرا؟زمانی که شما تصمیم دارین یک اپلیکیشن موبایلی رو کرک کنید و یا محدودیت‌هایی که داره رو Bypass کنید، تو اکثر مواقع نیازه که اون اپلیکیشن روی یک دیوایس Root شده نصب و اجرا بشه، حالا موضوعی که هست Root کردن یک Emulator با یک کلیک انجام میشه ولی Root کردن دیوایس واقعی (تو ورژن‌های جدید اندروید) دردسرهای خاص خودش رو داره...پس اگه اپلیکیشنی داریم که امکان اجرا شدن روی Emulator رو داشته باشه در واقع اومدیم کار اون شخصی که تصمیم داره محدودیت‌های اپلیکیشن ما رو Bypass کنه رو یک قدم راحت‌تر کردیم...البته انجام این مورد تو بعضی از اپلیکیشن و بازی‌ها باعث کاهش رضایت کاربری میشه، مثلا خیلی‌ها ترجیح میدن بازی‌های موبایلی رو روی شبیه ساز‌های دسکتاپی اجرا کنن، تو همچین حالتی باید ببینیم پیاده‌سازی این محدودیت، تا چه حد روی تجربه و رضایت کاربری تاثیر گذاره و بعد تصمیم بگیریم که بهتره پیاده‌سازی بشه یا اینکه نه...از Root Detection استفاده کنیدهمونطور که تو مورد قبل توضیح دادم، شخصی که میخواد یک اپلیکیشن رو کرک کنه و یا محدودیت‌هایی که داره رو Bypass کنه اکثر مواقع نیاز داره که اپلیکیشن مورد نظرش روی یک دیوایس Root شده نصب و اجرا بشه (چه Emulator و چه دیوایس واقعی)، پس یکی دیگه از نکاتی که باید رعایت کنیم اینه که اجازه اجرا شدن اپلیکیشن در صورتی که دیوایس Root شده بود رو ندیم...خب قبل از اینکه بریم سراغ موارد بعدی چند نکته مهم رو در خصوص همین 3 مورد بالا توضیح بدم:پیاده سازی هر 3 مورد خیلی راحته و زمانی نمی‌گیره اصلا.هر 3 مورد رو میشه خیلی راحت Bypass کرد! احتمالا الان با خودتون بگین اگه میشه راحت Bypassشون کرد پس چکاریه که پیاده‌سازی بشن!اگه به همین 3-4 سال قبل برگردیم، Bypass این 3 مورد کار راحتی نبود (از این نظر که آموزش خوبی براش وجود نداشت)، ولی الان اگه یه سرچ کوچیک کنین کلی آموزش در خصوص نحوه Bypassشون پیدا می‌کنین که نحوه کار رو مرحله به مرحله توضیح داده...در حال حاضر روش رایج برای Bypass کردن این 3 مورد، استفاده از Frida و hook کردنه (Frida کاربردهای خیلی زیادی تو زمینه امنیت، کرک و مهندسی معکوس داره و اینجوری نیست که فقط مخصوص اندروید و Bypass این 3 مورد بالا باشه)اگه با Frida آشنا نیستین و یا میخواین در موردش بیشتر بدونین این دو لینک میتونه مفید باشه: https://www.corellium.com/guides/6181264-using-frida-to-find-hooks  https://mobsecguys.medium.com/exploring-native-functions-with-frida-on-android-part-2-98b97e89eb3d خبر خوب و یا شایدم بد اینکه استفاده از Frida اصلا سخت نیست...باز برگشتیم به اینکه اگه 3 مورد بالا رو میشه راحت Bypass کرد پس چه فایده داره پیاده سازیشون!؟نکته اینجاست که اگه شما بیاین برای پیاده سازی 3 مورد بالا، از کدهایی که به صورت عمومی در دسترس همه‌ست (مثلا از Stackoverflow ، Github و خلاصه هر سورس کدی که به صورت عمومی در دسترس همه هست) استفاده کنید، شک نکنید که خیلی راحت میشه Bypassش کرد... چکار کنیم پس...؟ اول از همه بگم که ما فقط میخوایم کاری کنیم که Bypassشون سخت‌تر بشه، وگرنه اینکه راهی باشه که بیایم انجام بدیم و کلا جلوی Bypassشون گرفته شه نداریم اصلا...بیایم خودمون رو بذاریم جای اون نفر (اونی که میخواد Bypass کنه)، تو اولین مرحله قطعا میریم سراغ اسکریپت‌های آماده Frida و خلاصه هر اسکریپتی که توی اون زمینه (مثلا Bypass کردن SSL Pinning) پیدا میشد رو تست میکردیم ببینیم نتیجه میده یا نه، خب بیاین فرض کنیم هرچی اسکریپت آماده بود رو تست کردیم و نتیجه نگرفتیم، حالا این برمیگرده به ما (ما اینجا منظور شخصی که میخواد Bypass کنه)، اینکه Bypass کردنش اونقدری برامون اهمیت داره که بریم بخاطرش اپلیکیشن رو دیکامپایل کنیم و بعد سورس کدش رو تحلیل کنیم و بعد اسکریپت hook اختصاصی بنویسیم براش یا نه...اگه اون نفر حوصلشو داشته باشه و مهم‌تر اینکه دانشش رو هم داشته باشه (البته کار سختی نیست) و بیاد اسکریپت اختصاصی و مرتبط با سورس کدی که تحلیل کرده رو بنویسه و با اون hook کنه که خب هیچی، Bypass میکنه و تموم...ولی بیاین فرض رو روی این بذاریم که اون نفر اگه با اسکریپت‌های آماده به نتیجه نرسه کلا بیخیال میشه، حالا یا حوصلش نیومده سورس کد رو تحلیل کنه، یا اصلا دانشش رو نداشته که بخواد خودش اسکریپت بنویسه و یا هر دلیل دیگه‌ای،  پس کافیه یه نگاهی به اسکریپت‌های آماده‌ای که در این خصوص به صورت عمومی توی اینترنت موجوده بندازیم و بعد کدمون رو جوری بنویسیم که حداقل با اسکریپت‌های آماده نشه Bypassش کرد...اکثر اسکریپت‌های آماده Frida رو میتونین اینجا پیدا کنین: https://codeshare.frida.re/ولی بازم تاکید کنم که تو بهترین حالت فقط میتونیم Bypass رو سخت‌تر کنیم، نمی‌تونیم غیرممکنش کنیم، در واقع ما با رعایت نکته بالا فقط میایم جلوی bypass شدن با اسکریپت‌های آماده رو می‌گیریم، فقط همین...و نکته بعدی اینکه حتی اگه حوصلتون نمیاد که پیاده سازی 3 مورد بالا رو به این روش که توضیح دادم انجام بدین، بازم حتما انجامشون بدین حتی با روش و کدهای آماده‌ای که به صورت عمومی تو اینترنت موجوده، چون بازم حداقل اومدین یک پله امنیت رو بردین بالاتر...کدها رو Obfuscate کنیداین مورد که دیگه نیاز به توضیح نداره و همه میدونیم که باید انجام بدیم. خیلی مهمه که خوندن و تحلیل سورس کد برای کسی که اپلیکیشن ما رو دیکامپایل میکنه سخت باشه، درسته که بازم اگه وقت بذاره به نتیجه میرسه ولی اگه کدها تا یه حد خوبی Obfuscate شده باشن حداقلش اینه که برای تحلیل کدها زمان زیادی باید بذاره و ممکنه این وسط بیخیال بشه...دسترسی کاربرها رو به IP‌ داخل کشور محدود کنیداگه اپلیکیشنی دارید که فقط مختص کاربرای ایرانیه و خارج از کشور هیچ کاربردی نداره، دسترسی کاربراتون رو فقط به IP های داخلی محدود کنین، چرا این کارو کنیم؟زمانی که شخصی بخواد (منظور شخص کرکر و کسی که میخواد محدودیت‌های اپلیکیشن ما رو Bypass کنه) سمت سرور اپلیکیشن ما وقت بذاره، مثلا میخواد چک کنه ببینه آسیب پذیر هست یا نه، اگه اپلیکیشن ما فقط به IP های داخل کشور محدود شده باشه، این باعث میشه یکسری چالش برای اون نفر به وجود بیاد، قطعا جلوش رو نمی‌گیره ولی ما باید هر چیزی که باعث میشه کار رو براش سخت‌‌تر کنه رو انجام بدیم...شاید بگین خب این روزا که اکثر کاربرای ایرانی از VPN استفاده میکنن، اون موقع چی! بله درسته این موضوع رو هم باید در نظر گرفت که پیاده سازی این روش تا چه حد می‌تونه روی رضایت و تجربه کاربری تاثیر منفی داشته باشه و بعد برای پیاده سازی کردن یا نکردنش تصمیم بگیریم...در صورت فعال بودن VPN service اجازه کار با اپلیکیشن رو ندیدیکی دیگه از مهم ترین نکاتی که باید رعایت کنیم اینه که در صورت فعال بودن VPN Service اجازه ارسال/دریافت ترافیک رو ندیم، چرا اینکارو کنیم؟ساده‌ترین دلیلش اینه که برای دیدن ترافیک میشه به جای Proxy از VPN هم استفاده کرد، پس ما اجازش رو نمیدیم...دقت کنید که این مورد نباید فقط زمان اجرای اپلیکیشن بررسی بشه، باید به صورت Listener باشه...و اینکه باز اینجا سوال پیش میاد که الان اکثر کاربرای ایرانی از VPN استفاده میکنن، اگه این محدودیت رو بذاریم که به مشکل میخورن! بله درسته، برای پیاده سازی این محدودیت لازمه بین رضایت کاربری و امن‌تر کردن اپلیکیشن یکی رو انتخاب کنیم...مقادیر مهم و حساس رو به هیچ وجه به صورت Hardcode استفاده نکنیداین مورد هم که دیگه نیاز به گفتن نداره، به هیچ عنوان مقادیر حساس مثل API Key ، کلید رمزنگاری ، توکن و کلا هر چیزی که احساس میکنید حساسه نباید به صورت Hardcode باشه. پس چکار کنیم؟ پارامترهای حساس رو از سرور بگیرین، شاید بگین خب چکاریه اینجوری که بازم اون نفر راه برای دیدن این پارامترها داره!بله درسته، ولی حداقل دیگه به صورت مستقیم توی سورس کد نیست، یادتون باشه کلا هدف اینه که کارو برای اون نفر سخت‌تر کنیم نه اینکه کامل جلوشو بگیریم (چون نمی‌تونیم)نکته بعدی در همین خصوص اینکه تو خیلی از اپلیکیشن‌ها پارامترهای حساس بعد از گرفتن از وب سرور، به صورت plain داخل shared preferences نگه‎‌داری میشن که اینم روش درستی نیست (درسته که تو حالت عادی دسترسی به shared preferences نیاز به روت بودن دیوایس داره، ولی حتی با فرض پیاده سازی root detection و اینکه نشه Bypassش کرد، بازم میشه به shared preferences دسترسی داشت)، پس بهتره پارامترهای حساسی که قراره local نگه‌داری بشن، حتما به صورت Encrypt شده باشه...برای اجرا شدن Activity ها محدودیت بذاریداز اونجایی که Activityها رو میشه با ADB هم بالا اورد (نسبت به زبان و فریمورک فرق میکنه روش کار)، یکی از نکاتی که باید رعایت بشه اینه که اجرا شدن Activityهای حساس رو به پارامترهایی که از Activity قبلی میگیره محدود کنید... این مورد زمانی حساسیتش بیشتر میشه که کاربر بتونه یک Activity رو (به صورت مستقیم) بالا بیاره و ترافیک ارسال/دریافت کنه... البته درسته که با ADB میشه پارامترهای مورد نیاز رو هم ارسال کرد، ولی اینجوری اون نفر نیاز داره که اپلیکیشن رو دیکامپایل و سورس کد رو تحلیل کنه تا بتونه تعداد، طول و فرمت صحیح هر پارامتر رو متوجه بشه، که خب این باعث میشه ازش زمان زیادی بگیره و هدف ما هم فقط همینه...روش دیگه‌ش هم اینه که مشخص کنیم کدوم اپلیکیشن و class اجازه run کردن هر Activity رو داره، یک راهش اینجوری میشه:مقادیر مهم و حساس رو داخل shared preferences نذارینتا جای ممکن مقادیر حساس رو توی shared preferences نذارین، اگرم میذارین جوری بذارین که اون شخص با نگاه اول متوجه نشه که هر پارامتر چه کاربردی داره و یه پیشنهاد شاید عجیب اینکه اگه پارامترهای حساسی رو داخل shared preferences میذارین، همراه با پارامترهای اصلی یکسری پارامتر الکی با اسم‌های ظاهرا حساس و مقادیر رندوم رو هم داخل shared preferences بذارید ، هدف اینه که زمانی که اون شخص shared preferences رو چک کرد حداقل سریع متوجه نشه هر پارامتر چیه و برای چه استفاده‌ای اونجا گذاشته شده... (تاثیر داره)نکته بعدی اینکه اگه پارامتر حساسی رو میخواین داخل shared preferences نگه دارین، در کنار اینکه باید به صورت Encrypt شده باشه، یک مقدار رندوم با طول مشخص رو به قبل و بعدش اضافه کنید، اینجوری:ABC [Encrypted] DEFبرای Decrypt هم که خب اول مقادیری که اضافه کردین رو حذف میکنید ازش...و باز تاکید میکنم که هدف اینه که کار رو برای اون شخص وقت‌گیر تر کنیم، وگرنه خب بله با یه نگاه به سورس کدی که دیکامپایل کرده متوجه اصل داستان میشه... فقط دیتای مورد نیاز رو داخل response قرار بدیناین مورد رو با مثال توضیح میدم، خیلی از اپلیکیشن‌هایی که روشون کار کردم دیتا ای که قرار بوده به کاربر نشون داده نشه رو هم داخل response گذاشته بودن و بعد سمت کلاینت چک میکردن که اون دیتا رو به کاربر نشون بدن یا ندن، مثلا اگه کاربر premium بود بهش نشون بدن، اگه نبود ندن... (ولی کل دیتا داخل response هست!)البته این موضوع فقط مختص به این حالت نمیشه، خیلی وقتا توی response دیتا ای هست که هیچ کاربردی نداره (یعنی اگه نباشه هم هیچ مشکلی پیش نمیاد) و همینجاست که اون نفر احتمال داره متوجه چیزایی بشه که نباید بشه و یا اینکه حداقل شاید به عنوان یک hint برای ادامه کار بتونه از اون دیتا استفاده کنه...پس دقت کنید که اگه قراره محتوایی بر اساس سطح دسترسی کاربر بهش نشون داده بشه، اون شرط کاملا باید سمت سرور بررسی بشه و نکته دوم اینکه response فقط باید شامل دیتا ای باشه که بهش نیاز داریم، اگه پارامتری رو نیاز نداریم چرا اصلا باید داخل response بذاریمش!مدیریت قابل Export بودن کامپوننت‌هاهمونطور که میدونید خیلی وقت‌ها نیاز داریم Service، Activity و یا Receiverی داشته باشیم که اپلیکیشن‌های دیگه بتونن اجراش کنن و یا بهش دیتا ارسال کنن (یا هر دو)، که خب توی این حالت از قابلیت Export بودن کامپوننت‌ها استفاده می‌کنیم...ولی خیلی وقت‌ها کامپوننت‌هایی که نیازی به دریافت دیتا و یا run شدن خارج از اپلیکیشن ندارن هم به اشتباه مقدار Export برابر با True در نظر گرفته میشه براشون (دقت کنید که در صورتی که intent-filter داشته باشیم، به صورت پیشفرض مقدار Exported برابر با True در نظر گرفته میشه مگه اینکه Falseش کنیم)خلاصه که به قابل Export بودن کامپوننت‌ها دقت داشته باشید و اگرم کامپوننتی نیاز داره که قابل Export باشه، در صورت امکان محدودیت بذارید براش که تا جای ممکن امن‌تر بشه...اگه میخواین نکات بیشتری رو در این خصوص بدونین، لینک‌های زیر میتونه مفید باشه: https://cwe.mitre.org/data/definitions/926.html  https://tedblob.com/android-12-safer-component-exporting/  https://hackerone.com/reports/258460 ترافیک رو انکریپت کنیدیکی دیگه از نکاتی که برای امن‌تر شدن اپلیکیشن‌مون میتونیم انجام بدیم اینه که ترافیک رو انکریپت کنیم، فقط چند نکته:شاید عجیب باشه ولی خیلی از اپلیکیشن‌ها هستن که این مورد رو رعایت کردن ولی کاری که انجام دادن این بوده که دقیقا رفتن یه تیکه کد رو از یه جا پیدا کردن و دقیقا همون رو حالا با یکم تغییر جزئی استفاده کردن و بدتر اینکه حتی کلیدی که به فرض توی github یا حالا هرجایی که ازش کپی کردن به عنوان sample گذاشته بوده رو هم تغییر ندادن! تو همچین حالتی با یکم وقت گذاشتن روی سورس کد، میشه ترافیک رو Encrypt کرد...نکته دیگه اینکه اگه ترافیک رو Encrypt می‌کنید، اینجوری نباشه که خیالتون ازش راحت باشه و با خودتون بگین حالا که داریم ترافیک رو Encrypt شده دریافت/ارسال می‌کنیم، پس کسی امکان دستکاری پارامتر‎‌ها رو نداره، کاملا نیازه که سمت سرور بعد از Decrypt شدن، پارامترها Validate و Sanitize بشن... یه نکته مهم دیگه که البته نیازی به گفتن نداره اینکه هر چقدرم رمزنگاری پیچیده‌ای رو انتخاب کرده باشین، بازم اگه اون نفر وقت بذاره و دانشش رو داشته باشه امکان Decrypt هست، پس در واقع ما با Encrypt کردن ترافیک فقط اومدیم یک مرحله امنیت رو بردیم بالاتر و امکان دستکاری ترافیک رو سخت‌تر کردیم (غیر ممکن نکردیم)حتما از امن بودن API مطمئن بشیدشکی نیست که اگه اپلیکیشنی داریم که با وب سرور در ارتباطه، در کنار این که باید روی امنیت اپلیکیشن (سمت کاربر) کار کنیم، باید روی امنیت APIش هم کار کنیم و این 2 تا در کنار همدیگه‌ست که باعث امن شدن یک اپلیکیشن میشه، اینکه چجوری میتونیم یک API امن داشته باشیم نیاز به توضیحات زیادی داره، سر فرصت یک پست مثل همین پست رو در خصوص روش‌های امن کردن API هم آماده کنم...آخرین نکته...این مورد خیلی ارتباط مستقیمی با عنوان پست نداره ولی دیدم اینم گفتنش بد نیست...خبلی از سازمان/شرکت‌ها هستن که در کنار وب سایت، اپلیکیشن موبایلی هم دارن. اما از اونجایی که بیشتر کاربراشون از وب‌ سایت استفاده میکنن یه جورایی اپلیکیشن موبایلی‌شون افتاده کنار و اهمیتی بهش نمیدن...حالا این چه مشکلی ایجاد میکنه؟ بیاین فرض کنیم روی امنیت وب‌سایت کاملا کار شده و کسی که قصد پیدا کردن آسیب پذیری از وب سایت اون سازمان/شرکت رو داره موفق نمیشه، تو این مرحله یکی از کارایی که اون شخص میتونه انجام بده تحلیل اپلیکیشن اون سازمان با هدف بررسی API، پارامترها و خلاصه آشنا شدن با Flow ترافیکه...پس اگه سازمان/شرکتی هستین که هم وب سایت دارین و هم اپلیکیشن موبایلی، ولی از اونجایی که بیشتر کاربراتون از وب سایت استفاده میکنن اپلیکیشن موبایلی‌تون افتاده کنار، حتما نیازه که تا قبل از اینکه روی امنیت اپلیکیشن موبایلی‌تون کار نکردین، فایلش رو کامل از روی سرور بردارین (چندین مورد پیش اومده که بهشون اطلاع دادم اپلیکیشن‌اتون آسیب پذیره ولی کاری که کردن این بوده که لینک دانلودش رو از روی وب سایت برداشتن! راه برای پیدا کردن لینک های حذف شده زیاده، باید خود فایل رو حذف کنید)به نظرم تا همینجا کافیه... از چیزی که انتظارش رو داشتم خیلی طولانی‌تر شد این پست! سر فرصت دو پست در خصوص روش‌های عمومی امن کردن یک وب سایت و API هم آماده میکنم...تو این پست قصد داشتم یکسری از مهم‌ترین نکات عمومی که رعایت کردنشون باعث امن‌تر شدن اپلیکیشن‌های موبایلی میشه رو توضیح بدم، امیدوارم براتون مفید بوده باشه...نکته‌ مهمی که انتظار دارم بعد از خوندن این پست بهش رسیده باشین اینه که برقراری امنیت مثل یک زنجیر میمونه، اگه یک قسمت از کار درست انجام نشه باعث میشه زمانی که برای امن کردن سایر قسمت‌ها گذاشتیم هم بی فایده بشه...این رو هم حتما در نظر بگیرید نکاتی که توضیح دادم نکات عمومی‌ان، یعنی اگه کلشون رو هم به بهترین شکل انجام بدیم بازم به این معنی نیست که یک اپلیکیشن امن داریم، امن کردن هر اپلیکیشن نسبت به فیچرهایی که داره روش پیاده‎‌سازی خاص خودش رو داره...و آخرین مورد اینکه اگه این پست براتون جالب بود، شاید ویدیو‌های زیر هم براتون جالب باشه:دوره کرک اپلیکیشن و بازی‌های اندرویدی (10 جلسه) https://www.aparat.com/v/8uJ4L تحلیل و بررسی سورس کد اپلیکیشن‌های جعلی ثنا که یه مدت خیلی زیاد شده بود: https://www.aparat.com/v/LW94a شاد و موفق باشین...</description>
                <category>عماد عابدینی</category>
                <author>عماد عابدینی</author>
                <pubDate>Wed, 07 Sep 2022 20:28:54 +0430</pubDate>
            </item>
                    <item>
                <title>پیاده‌سازی Refresh Token در Nodejs</title>
                <link>https://virgool.io/Rocket/%D9%BE%DB%8C%D8%A7%D8%AF%D9%87-%D8%B3%D8%A7%D8%B2%DB%8C-refresh-token-%D8%AF%D8%B1-nodejs-kxw8fpsmozdr</link>
                <description>خیلی وقت‌ها تو پروژه‌های کوچیک زمانی که میخوایم احراز هویت رو به صورت Token Base پیاده‌سازی کنیم، فقط AccessToken رو در نظر می‌گیریم، در صورتی که در واقع میشه گفت تو بیشتر پروژه‌ها Token Base بدون پیاده سازی RefreshToken کامل نیست. SPA = Single-Page Application;    AS = Authorization Server;    RS = Resource Server;    AT = Access Token; RT = Refresh Token.از مهم ترین دلایلی که نیاز به RefreshToken داریم اینه که:فرض کنید AccessToken کاربر به هر دلیل لو میره، پس باید راهی باشه که کاربر بتونه AccessToken قبلیش رو Block کنه.نیاز داریم که Logout داشته باشیم. البته هر دو حالت بالا رو میشه بدون استفاده از RefreshToken هم پیاده‌سازی کرد، اما راه اصلی اینه که از RefreshToken استفاده کنیم...نکته مهم دیگه ای که میشه بهش اشاره کرد اینه که از نظر امنیتی نباید ExpireTime برای AccessToken طولانی باشه، چون همونطور که بالاتر توضیح دادم ممکنه به هر دلیل توکن کاربر لو بره. پس میایم ExpireTime رو برای AccessToken یه زمان کوتاه مثلا در حد 15 دقیقه  ولی در عوض این زمان رو برای RefreshToken طولانی تر مثلا در حد یک هفته در نظر می‌گیریم.و بعد زمانی که به Expire شدن AccessToken نزدیک شدیم، کاربر درخواست Refresh میده تا یک AccessToken دیگه بهش اختصاص داده بشه برای 15 دقیقه.اگه با RefreshToken آشنا نیستین و یا دوست دارین بیشتر در موردش بدونین، لینک‌های زیر میتونه مفید باشه: https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/  https://developer.okta.com/docs/guides/refresh-tokens/main/  https://auth0.com/learn/refresh-tokens/ خلاصه که بخش Authentication یکی از پروژه ها رو جدا کردم گذاشتم Github که شاید بتونه برای کسی مفید باشه.(پروژه با Nodejs و Nestjs پیاده سازی شده و توضیحات مرتبط باهاش رو تو Github گذاشتم)تو حالت‌های زیر میتونه قابل استفاده باشه:زمانی که میخواین Authentication رو تو پروژه‌های Nestjsی پیاده سازی کنین.برای حالتی که پروژتون Nodejsی نیست ولی میخواین احراز هویت رو به صورت سرویس جدا داشته باشین، که خب تو این حالت هم کار خیلی سادست، User Credentials رو میفرستین برای سرویس Authentication ، بهتون توکن میده، حالا از روی JWT-Secret-Key (که هر دو طرف باید یکی باشه) میاین valid بودن توکن‌ها رو چک می‌کنید. تو اولین فرصت Authorization هم بهش اضافه می‌کنم که کامل‌تر بشه. (از CASL استفاده می‌کنم)لینک repository پروژه: https://github.com/EmadAbedini/RefreshToken اینطوری نیست که هر جا Token Base داریم باید حتما Refresh Token هم داشته باشیم... بیشتر توی این حالت ها نیازه که پیاده سازی بشه:* توی Single sign-on (SSO) ها* زمانی که معماری Microserviceه و سرویس احراز هویت جداست.* زمانی که معماری Monolithicه و احراز هویت بخشی از پروژست.شاد و موفق باشین :)</description>
                <category>عماد عابدینی</category>
                <author>عماد عابدینی</author>
                <pubDate>Fri, 24 Jun 2022 10:23:51 +0430</pubDate>
            </item>
                    <item>
                <title>استفاده از Currying در JavaScript</title>
                <link>https://virgool.io/Rocket/%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%D8%A7%D8%B2-currying-%D8%AF%D8%B1-javascript-efri10b7qmcd</link>
                <description>امروز سر یه Merge request صحبت از Currying شد که البته روش خیلی ساده‌ایه ولی چون برای بعضی از بچه ها جالب بود گفتم این جا هم بذارمش شاید برای کسی مفید باشه.خلاصه میگم که وقتتون رو نگیرم...(تو همه زبان ها میشه از Currying استفاده کرد ولی مثال ها رو با JavaScript آماده کردم )تو ساده‌ترین حالت برای Function composition از این روش استفاده می‌کنیم:ولی بعضی وقتا مثل همین حالت بالا، تعداد پرانتزها زیاد و باعث نامرتب شدن کدها میشه...برای حل این مشکل معمولا میایم از Higher-order Functions ها استفاده می‌کنیم که بتونیم توابعی که داریم رو ترکیب کنیم و یا اگه پروژه Js ای هست میتونیم از Lodash استفاده کنیم که میشه اینطوری:مشکل زمانی پیش میاد که توابعی داریم که بیشتر از یک آرگومان دارن، مثلا:اینجا چند تا موضوع پیش میاد: ورودی pipe حتما باید یک تابع رو return کنه (نمیتونیم از call شده‌ی تابع استفاده کنیم) موقعی که از pipe استفاده می‌کنیم خروجی تابع سمت چپ به عنوان ورودی تابع سمت راست داده میشه.  پس با دونستن این 2 مورد میتونیم این مشکل رو با استفاده از Currying حل کنیم، که میشه اینطوری:اگه کنجکاوین با Currying بیشتر آشنا بشین این دو تا لینک میتونه کمک کنه: https://en.wikipedia.org/wiki/Currying#:~:text=In%20mathematics%20and%20computer%20science,each%20takes%20a%20single%20argument  https://javascript.info/currying-partials Currying (post&#039;s cover image)</description>
                <category>عماد عابدینی</category>
                <author>عماد عابدینی</author>
                <pubDate>Tue, 31 May 2022 21:24:11 +0430</pubDate>
            </item>
                    <item>
                <title>آسیب‌پذیری Class-Validator نسبت به XSS و SQLi</title>
                <link>https://virgool.io/@emad_abedini/%D8%A2%D8%B3%DB%8C%D8%A8-%D9%BE%D8%B0%DB%8C%D8%B1%DB%8C-class-validator-%D9%86%D8%B3%D8%A8%D8%AA-%D8%A8%D9%87-xss-%D9%88-sqli-pw063ugo0dga</link>
                <description>اگه برای sanitize و بررسی valid بودن ورودی‌ها از class-validator استفاده می‌کنید، دقت کنید که نسبت به XSS و SQL_Injection آسیب‌پذیره.امروز که برای یک پروژه NestJSی می‌خواستم ازش استفاده کنم تو مستنداتش تابعی مرتبط با جلوگیری از این دو آسیب‌پذیری پیدا نکردم، اول فکر کردم شاید Built-in داره این کارو میکنه... (ولی اینطور نبود)یه پارامتر به اسم forbidUnknownValues داره که اگه trueش کنی تا حدودی جلوی این دو آسیب‌پذیری رو می‌گیره (فقط تا حدودی)در مورش سرچ کردم، SNYK-ID هم گرفته حدودا 6 ماه پیش، ولی با این حال این مشکل هنوز برطرف نشده...! (در حال حاضر ورژنش v0.13.2)، فقط ظاهرا 6 ماه پیش به forbidUnknownValues تو مستندات اشاره‌ای نشده بوده ولی الان تو مستندات اوردنش با این توضیح که بهتره true بشه. https://security.snyk.io/vuln/SNYK-JS-CLASSVALIDATOR-1730566 چند لینک مرتبط با این موضوع:https://github.com/typestack/class-validator/issues/1422https://nvd.nist.gov/vuln/detail/CVE-2019-18413https://vuldb.com/?id.144159----خلاصه که اگه می‌خواستین از class-validator استفاده کنید، تا زمانی که این مشکل رو برطرف نکرده پیشنهاد می‌کنم ورودی‌ها رو بعد از دریافت حتما escape کنید.درسته که اگه از یه ORM خوب استفاده کنیم و Front رو هم مثلا با Vue,React و Angular بزنیم، تا حد خیلی زیادی جلوی این دو آسیب‌پذیری گرفته میشه، ولی اینکه بخوایم فقط خروجی رو encode کنیم و کنترلی روی ورودی‌ها نداشته باشیم منطقی نیست.در خصوص ORM هم همونطور که میدونید XSS از نوع Reflected و Dom-Based ارتباطی با دیتابیس نداره.بعد از true کردن پارامتر forbidUnknownValues هم فقط تا حدودی امن تر میشه (نه کامل)میشه از توابعی که در حال حاضر داره هم برای جلوگیری از این دو آسیب‌پذیری استفاده کرد ولی اون توابع کاربرد اصلیشون این نیست.برای جلوگیری از این دو آسیب پذیری به خصوص XSS، فقط escape کردن ورودی کافی نیست (فرض می‌کنیم bypass شد، پس بهتره در کنار escape کردن، چیزای دیگه رو هم در نظر بگیریم)این پست رو لینکدین هم گذاشته بودم، اونجا یه سوال پرسیدن که چون سوال خوبی بود گفتم اینجا هم بذارمش:متن سوال:خود nest جلو xss رو نمیگیره؟ یا اگه اون پکیج Helmet رو با nest تلفیق کنیم کارساز نیست؟از اونجایی که XSS یه آسیب‌پذیری Client-sideه، پس Nest نمیتونه تاثیر خاصی برای جلوگیری از این آسیب‌پذیری داشته باشه.در خصوص helmet، فقط میاد یکسری header اضافه میکنه و خودشم توضیح داده که:It&#x27;s not a silver bullet, but it can helpاینم باید در نظر بگیریم که خیلی وقتا آسیب‌پذیری‌ها به صورت chain شده‌ان و ممکنه خودشون به تنهایی تو پروژه ما هیچ خطری نداشته باشن، ولی موقعی که با هم ترکیب میشن باعث آسیب‌پذیری میشن، مثلا خیلی ها دیدم میگن پروژمون TokenBaseه و توکن هم گذاشتیم تو LocalStorage یا مثلا میگن کوکی هارو HttpOnly گذاشتیم پس قطعا مشکلی از نظر XSS نیست!! در صورتی که XSS مثل اینه که دسترسی به کنسول Browser داریم، با کنسول چه کارایی میشه انجام داد؟ با XSS هم میشه... (برای طولانی نشدن پست توضیح در خصوص CSP رو اینجا نیاوردم)در خصوص XSS خلاصه کار اینه که روی ورودی ‎ها و خروجی‌ها کنترل داشته باشیم، حالا چون این وسط احتمال bypass وجود داره پس بهتره در کنارش چیزای دیگه هم رعایت کنیم.شاد و موفق باشین...</description>
                <category>عماد عابدینی</category>
                <author>عماد عابدینی</author>
                <pubDate>Tue, 17 May 2022 22:31:57 +0430</pubDate>
            </item>
                    <item>
                <title>برای Mongo (فعلا) از Prisma استفاده نکنید!</title>
                <link>https://virgool.io/@emad_abedini/%D8%A8%D8%B1%D8%A7%DB%8C-mongo-%D9%81%D8%B9%D9%84%D8%A7-%D8%A7%D8%B2-prisma-%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%D9%86%DA%A9%D9%86%DB%8C%D8%AF-ueuhrnjswhjs</link>
                <description>چند وقتی بود که می‌دیدم تو بعضی از ویدیوهای یوتیوب تازگیا از #Prisma زیاد تعریف میکنن، سر همین گفتم یه نگاهی بهش بندازم.اگه با Prisma آشنا نیستین، یک ORM برای Nodejsه (مثل TypeORM، Mongoose، Sequelize و...) https://www.prisma.io/  https://medium.com/sliit-foss/a-next-generation-orm-prisma-3c3f2b46bd5b برای تست کردنش یه سرویس خیلی کوچیک CRUD رو با Nodejs بالا اوردم تا بتونم تست هارو انجام بدم.نتیجه کار به این صورت شد:برای دیتابیس های SQLی، به نظر مشکلی نداره و میتونه انتخاب خوبی باشه، ولی برای Mongo یک مشکل خیلی بزرگ داره!اونم اینکه حتی تو حالت Development هم شما رو مجبور میکنه که از Mongodb Atlas استفاده کنید! (در اصل گفته نیاز به Replica Set داره)https://github.com/prisma/prisma/issues/11860https://github.com/prisma/prisma/discussions/9994https://github.com/prisma/prisma/issues/8266درسته که توی Production بهتره از Replication استفاده کرد (و تو خیلی از پروژه ها یک نیاز اساسیه) ولی اینکه توی Development هم مجبور کنه، به نظرم منطقی نیست!بعد از 2-3 روز سرچ کردن، تموم روش‌هایی که برای حل این مشکل پیدا کردم رو تست کردم ولی هیچ کدوم به طور کامل مشکل رو برطرف نکرد!البته خود Prisma هم تو مستنداتش گفته: Prisma Client requires a MongoDB replica set. We note this constraint in our MongoDB guides under &quot;Prerequisites&quot;. It is possible to set up replica sets locally, but Mongo Atlas is definitely the easiest way to get started.(روشی که تو مستنداتش برای حالت local پیشنهاد داده مشکل رو به صورت کامل برطرف نمیکنه)خلاصه که اگه برای پروژه‌های Nodejsی میخواین یک ORM خوب و مطمئن رو انتخاب کنید: (#نظر_شخصی)برای SQLی ها :پیشنهاد اول Prisma و پیشنهاد دوم TypeORM (از نظر Syntaxی به نظرم Prisma بهتر کار کرده و استفاده ازش حس بهتری بهتون میده)برای Mongo:پیشنهاد اول Mongoose و پیشنهاد دوم TypeORMالبته Mongoose یه مشکلی داره، اگه بخواین بین populate ها فیلترهای تو در تو بزنین فقط تا 3 سطح رو میتونین از توابع داخلیش استفاده کنید ( ولی خب نمیشه از این نظر بهش ایراد گرفت، چون نباید ساختار رو جوری در نظر گرفت که نیاز به چندین سطح populate باشه)و در آخر اینکه، به نظرم در آینده نزدیک احتمالا Prisma محبوب‌تر از چیزی که الان هست میشه (البته امیدوارم زودتر مشکلی که توضیح دادم رو برای Mongo برطرف کنه)</description>
                <category>عماد عابدینی</category>
                <author>عماد عابدینی</author>
                <pubDate>Sat, 14 May 2022 22:15:22 +0430</pubDate>
            </item>
                    <item>
                <title>تجربه 3 ماه کار با NestJS و تفاوت اصلیش با Express</title>
                <link>https://virgool.io/@emad_abedini/%D8%AA%D8%AC%D8%B1%D8%A8%D9%87-3-%D9%85%D8%A7%D9%87-%DA%A9%D8%A7%D8%B1-%D8%A8%D8%A7-nestjs-%D9%88-%D8%AA%D9%81%D8%A7%D9%88%D8%AA-%D8%A7%D8%B5%D9%84%DB%8C%D8%B4-%D8%A8%D8%A7-express-b6iw5iwql5lu</link>
                <description>اگه برای پروژه‌‌های Nodejsی از Express استفاده می‌کنین، پیشنهاد می‌کنم حتما یه نگاهی به NestJS هم بندازین...حدود 3 ماه پیش بود که برای پروژه‌ای که قرار بود بکند Nodejsی باشه بعد از چند روز تحقیق تصمیم گرفتیم به جای Express از Nest استفاده کنیم...گفتیم یکم باهاش کار می‌کنیم اگه دیدیم راضی نیستیم برمی‌گردیم سر Express.امروز اون پروژه تموم شد و از اینکه با NestJS زدیمش کامل راضی‌ایم... بزرگ ترین خوبی ای که Nest داره اینه که ساختار مشخص و مرتبی داره. درسته که حتی بدون استفاده از هیچ فریمورکی هم میشه دقیقا همین ساختار رو ایجاد کرد ولی موضوعی که هست اینه که Nest به طور پیشفرض یعنی زمانی که شما پروژه جدیدی ایجاد می‌کنین یه ساختار مشخص داره و مستنداتش هم جوریه که شما رو مجبور می‌کنه که تا حد نسبتا خوبی ساختار مشخص و کدهای مرتبی داشته باشین... البته که از یه جایی به بعدش دیگه به اون برنامه نویس برمیگرده که کدهارو چجوری بزنه.همونطور که می‌دونین تو Express ساختار مشخصی نداریم و هر تیم میاد بر اساس دانش و سلیقه خودش یک ساختار رو در نظر می‌گیره، به همین خاطر تو پروژه‌های Expressی ساختاری که یک تیم برای پروژشون در نظر گرفتن ممکنه تو بعضی از قسمت‌ها با تیم دیگه فرق کنه. ولی این موضوع تو Nest به نظرم خیلی کمتر پیش میاد چون ساختار کلی تو Nest مشخصه.در واقع Express با شعار unopinionated اومد که می‌گفت هرکس با هر ساختاری که بخواد می‌تونه ازش استفاده کنه و همین باعث می‌شد codebase اکثر پروژه‌ها تفاوت داشته باشه ولی با Nest ساختار codebase پروژه قابل درک برای همه است...چند نمونه دیگه از خوبی‌های Nest : * معماری و ساختار پیشفرض Stable و Clean ( که البته این امکان رو هم دارین که تغییرش بدین)* نِست عملا یک wrapper هم هست، بهتون یه Abstraction Layer میده تو سطوح بالاتر که خیلی کارها رو آسون‌تر می‌کنه همونطور که Under Hood میتونید از Fastify هم استفاده کنید.* استفاده از DI و دیزاین پترن هایی مثل Singleton ( به صورت پیشفرض)درسته همه این ها تو Express هم موجوده و یا می‌تونیم داشته باشیم ولی کنار این‌ها فیچرهای امنیتی Nest، قابلیت‌ها و داکیومنت خیلی کاملش رو هم باید در نظر بگیریم.به نظرم احتمالا در آینده نزدیک Nest خیلی محبوب‌تر از چیزی که الان هست میشه، ولی خب قطعا هر کدوم خوبی‌های خاص خودشون رو دارن... https://betterprogramming.pub/stop-using-express-js-to-make-web-servers-faed1942eaf3  https://codeburst.io/why-you-should-use-nestjs-for-your-next-project-6a0f6c993be  https://learn.habilelabs.io/why-choose-nest-js-over-other-node-frameworks-68a13fa1e2c8 NestJS (Cover)</description>
                <category>عماد عابدینی</category>
                <author>عماد عابدینی</author>
                <pubDate>Mon, 02 May 2022 22:40:00 +0430</pubDate>
            </item>
                    <item>
                <title>دوره کرک اپلیکیشن و بازی‌های اندرویدی</title>
                <link>https://virgool.io/@emad_abedini/%D8%AF%D9%88%D8%B1%D9%87-%DA%A9%D8%B1%DA%A9-%D8%A7%D9%BE%D9%84%DB%8C%DA%A9%DB%8C%D8%B4%D9%86-%D9%88-%D8%A8%D8%A7%D8%B2%DB%8C-%D9%87%D8%A7%DB%8C-%D8%A7%D9%86%D8%AF%D8%B1%D9%88%DB%8C%D8%AF%DB%8C-%D8%A2%D9%BE%D8%A7%D8%B1%D8%A7%D8%AA-ygs0cnxmvcpz</link>
                <description>تو این دوره روش کرک اپلیکیشن و بازی‌های اندرویدی رو تو 10 جلسه (به صورت عملی) توضیح دادم...خلاصه‌ی کاری که این دوره انجام میدیم اینه که فایل بازی/اپلیکیشن رو Decompile می کنیم، بخشی که برای کرک شدن نیاز به تغییر داره رو تغییرش میدیم و مجدد خروجی می‌گیریم.لینک playlist آپارات:https://www.aparat.com/v/8uJ4L?playlist=1514801 https://www.aparat.com/v/8uJ4L?playlist=1514801 پیش‌نمایش پست</description>
                <category>عماد عابدینی</category>
                <author>عماد عابدینی</author>
                <pubDate>Sat, 16 Apr 2022 21:18:39 +0430</pubDate>
            </item>
                    <item>
                <title>بررسی سورس‌کد اپلیکیشن‌های جعلی ثنا</title>
                <link>https://virgool.io/@emad_abedini/%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-%D8%B3%D9%88%D8%B1%D8%B3-%DA%A9%D8%AF-%D8%A7%D9%BE%D9%84%DB%8C%DA%A9%DB%8C%D8%B4%D9%86-%D9%87%D8%A7%DB%8C-%D8%AC%D8%B9%D9%84%DB%8C-%D8%AB%D9%86%D8%A7-khkqn4dlr9fh</link>
                <description>(زمان انتشار اولیه این پست مربوط میشه به بهمن 1400، ظاهرا ویرگول بعد از عدم انتشار کردن پست و مجدد انتشار پست، زمان انتشار اولیه پست رو حذف میکنه!)تو این ویدیو سورس‌کد اپلیکیشن‌های جعلی ثنا که یه مدته خیلی زیاد شدن رو بررسی کردم.(البته در اصل این ویدیو رو برای اینستاگرام آماده کردم، به همین خاطر سطح توضیحاتش مقدماتیه، ولی بازم شاید براتون جالب باشه) https://www.aparat.com/v/LW94a پیش‌نمایش پست</description>
                <category>عماد عابدینی</category>
                <author>عماد عابدینی</author>
                <pubDate>Sat, 16 Apr 2022 20:40:45 +0430</pubDate>
            </item>
            </channel>
</rss>