با سلام مجدد خدمت دوستان. تمام تلاشم رو کردم که بتونم این مرحله دوم رو به موقع برسونم اگه جاییش نامفهومه ببخشید و اینکه میتونید توی لینکی که اخر پست میزارم سوالاتتون رو بپرسید.
خب توی این پست قراره که چالش شماره 2 رو خدمت دوستان حل کنم. این چالش با چالش قبلی فرقش این هست که شما توی این چالش نحوه کار با دیباگرها و یه مقدار انالیز ایستا یا همون static analysis رو یاد میگیرید و میتونید روال یک برنامه رو از ابتدا مرور کنید. و اینکه آشنا میشید با نحوه reverse کردن و انالیز کردن فایلهای باینری که به زبان های Native نوشته میشن. البته بازم این نکته رو بگم که این چالش ها صرفا یه مرور هست و شما باید وقت بزارید و در مورد مباحث مهندسی معکوس و تحلیل بدافزار مطالعه بیشتر بکنید.
چالش شماره 2 : Overlong
فایلهای مربوط به این چالش رو میتونید از اینجا دانلود کنید.
پیش نیازها:
- ویندوز 7 یا بالاتر
- دیباگر ollydbg یا x64dbg یا immunity
- ابزار strings
- آشنایی با زبان اسمبلی
- آشنایی با نحوه کار با دیباگر
خب شما یک فایل زیپ دارید که داخلش دوتا فایل هست یه فایل exe که هدف اصلی سوال هست و یک فایل Message که اطلاعاتی در مورد چالش به ما میده. در ابتدا وقتی فایل Message رو باز میکنیم به ما گفته شده که «کلمه رمز این مرحله به صورت هوشمندانهای، مخفی شده. اما اگه راه درستی رو انتخاب کنید زیاد پیدا کردنش زمان بر نیست».
خب این پیام هایی که توی چلنج ها میذارن هم میتونه مارو گمراه کنه هم میتونه به ما کمک کنه. اینجا گفته که به صورت هوشمندانه مخفی شده. خب پس حتما کارمون سخته. ولی از یه طرف هم گفته اگه راه درست رو برید، زیاد زمان بر نیست. اینجا متوجه میشیم که اونقدرا هم هوشمندانه نیست. که البته در انتها میفهمید اصلا هوشمندانه نبوده.
خب ما با یه فایل باینری رو به روییم که هیچی ازش نمیدونیم و میخوایم یه رمز ازش در بیاریم برا ورود به مرحله بعد.
اولین مرحله برا حل این چالش اینه که ما فایل رو یه بار اجرا کنیم ببینیم اصن چی هست و چه خروجی به ما میده. البته این نکته رو بگم که ما چون میدونیم این فایلها آلوده نیستند، به راحتی اونارو باز میکنیم ولی زمانی که شما با یه بدافزار مواجه هستید، به هیچ عنوان نباید فایل رو داخل سیستم عاملتون (Hostتون) باز کنید. باید اونو به یه محیط امن مثل ماشین مجازی ببرید و اونجا تستش کنید.
خب وقتی فایل رو باز میکنیم میبینیم یه پیغامی به ما میده و هیچ چیز خاصی داخل پیغام نیست.
حالا اولین مرحله توی تحلیل یک بدافزار و کلا یک فایل باینری همیشه استخراج اطلاعات اون فایل هست به صورت ایستا. که بهش میگن static analysis. این یعنی چی؟ یعنی بدون نیاز به اجرای برنامه، نحوه کارکرد فایل، و اطلاعات داخلش رو بررسی کنیم. یکی از مهمترین قسمت های آنالیز ایستا، استخراج رشته ها یا همون String های داخل فایل هست که میتونه به ما کمک کنه که بفهمیم چه چیزهایی ممکنه توی فایل وجود داشته باشه.
برای استخراج رشته ها ما دوتا راه داریم: یکی اینکه از ابزار strings که یه ابزار خیلی کم حجم هست استفاده کنیم و یکی هم استفاده از نرم افزار IDA Pro که خودش برامون این رشته ها رو استخراج میکنه.
اینجا ما راه اول رو میریم چونکه نیاز نداریم برای یه لیوان شیر، گاو بخریم. البته چون این مساله رو من قبلا حل کردم و راهش رو میدونم، دارم از این راه میرم که به جواب برسیم ولی شما باید همیشه ابزارهای آنالیز رو آماده و نصب شده داشته باشید تا بتونید سریع تحلیل رو انجام بدید.
خب ابزار Strings تحت خط فرمان اجرا میشه و کافیه که ما فایلمون رو بذاریم کنارش و این دستور رو بزنیم.
ابزار strings به صورت آماده توی تمام نسخه های لینوکس هست ولی توی ویندوز شما باید دانلودش کنید.
خب بعد از اجرای این دستور، همونطور که توی عکس میبینید، یه سری رشته بیخودی و به دردنخور برامون لیست کرده که چیزی ازش در نمیاد برا ما.
خب حالا قدم بعدی چیه؟ قدم بعدی اینه که ما فایل رو توی دیباگر اجرا کنیم و ببینیم چه اتفاقی داره میافته.
همه چیز توی همین پیغامی هست که نشون داده میشه و دوستان همیشه یادتون باشه که راه حل دقیقا جلوی پای شماست، نیاز نیس زیاد این ور اون ور برید.
خب من برای آنالیز برنامه توی دیباگر از دیباگرimmunity استفاده میکنم. ولی شما میتونید از هر کدوم از دیباگرا که باهاش راحت تر هستید استفاده کنید که لیستشون رو توی پیش نیازهای همین پست اوردم.
خب حالا یه کم براتون تاریخچه بگم و بگم اصن دیباگر چی هست.
دیباگر یا Debugger به نرم افزاری گفته میشه که کنترل اجرای یه برنامه رو به دست میگیره و به شما اجازه میده که اون برنامه رو قدم به قدم بتونید تحلیل کنید. دیباگرها به دو نوع usermode و kernel mode تقسیم میشن. که خب دسته اول میتونن برا شما نرم افزارهای سمت کاربر و دسته دوم نرم افزارها و درایورهای ویندوز و هسته ویندوز رو دیباگ کنن مثل Windbg که در ادامه این سری پستها بهش میرسیم.
یکی از بهترین دیباگرهای تاریخ، توسط یک انسان شریف به اسم Oleh Yuschuk ساخته شده که به نام ollydbg شناخته میشه. این دیباگر آخرین بار سال ۲۰۱۲ توسط همین شخص آخرین نسخه خودش رو عرضه کرد. ولی با گذر زمان هنوز از بهترین هاست. اما متاسفانه به دلیل تنبلی سازنده، نسخه ۶۴ بیتی این دیباگر دیگه منتشر نشد و به جای اون یه نفر دیگه اومد و با استفاده از همین نرم افزار، یه دیباگر جدید ولی با ایده گرفتن از olly تولید کرد با هدف ساخت دیباگر ۶۴ بیتی، که اسمش رو گذاشته x64dbg. البته این دیباگر برای فایل های ۳۲ بیتی هم کار میکنه ولی خب اسمش x64dbg هست.
حالا در مورد immunity هم بگم یه کم. این دیباگر برای شرکت امنیتی immunity هست. این شرکت میاد و کد دیباگر olly رو از صاحبش میخره و خودش توسعه میده و یه سری قابلیت بهش اضافه میکنه. مثلا میتونید توش اسکریپت پایتون اجرا کنید و از این حرفا. ولی در کل با هرکدوم راحت ترید کار کنید.
حالا ما اینجا immunity رو باز میکنیم و فایل هدف رو darg میکنیم. اینجا من سعی میکنم تا حدود کمی مباحث دیباگ رو بگم. ولی حتما برید و در مورد کار با دیباگرها و مفاهیم دیباگ مطالعه کنید و یاد بگیرید. وقتی برنامه داخل دیباگر لود شد، با دکمه play یا کلید F9 ، برنامه رو اجرا میکنیم. وقتی اینکارو کردیم بلافاصله دیباگر توی یه نقطه متوقف میشه. این نقطه رو بهش میگن EntryPoint. اینجا همون نقطه شروع برنامه هست. البته این نقطه هرگز ربطی به تابع main نداره. اینجا قبل از اینه که برنامه به تابع main برسه.
خب حالا برگردیم به چالش:
در این مرحله ما میخوایم قدم به قدم آنالیز کنیم و بریم جلو ببینیم چه خبره. با دکمه F8 میتونیم step over کنیم. یعنی هر خط کد اجرا میشه و به خط بعدی میره. ولی داخل تابع نمیره. یعنی چی؟؟ ما دوتا گزینه داریم برای اجرای قدم به قدم یا step by step توی دیباگر. یکی step over و یکی دیگه هم step into این دوتا فرقشون چیه؟ فرض کنید یه تابع وسط کد فراخوانی شده و برای ما مهم نیست که داخل این تابع چه کدی اجرا میشه. پس اینجا ما وقتی به خط مربوط به فراخوانی تابع میرسیم، باید F8 یا همون step over رو بزنیم که دیباگر داخل تابع نره و بیاد خط بعدی برنامه رو اجرا کنه. ولی بعضی وقتا نیازه که بریم داخل تابع و مراحل اجرای تابع رو هم ببینیم. اینجا باید از step into یا F7 استفاده کنیم که خب گیج کننده تر و زمان بر هست اما خیلی وقتا واقعا باید شما خط به خط برید جلو تا بفهمید چه اتفاقی داره میافته.
خیلی دوست دارم که اینجا براتون از دیباگ کردن بگم ولی واقعا خارج از حوصله هست و کلا مارو از هدف اصلیمون دور میکنه.
خب ما با زدن دکمه F8 میریم جلو تا وقتی که میبینم برنامه تموم شد و همون پیغام اومد روی صفحه. خب اینجا کلا ما ۱۰ خط بیشتر کد اسمبلی نداریم. برنامه فوق العاده کوچیکه، ولی شما توی دنیای واقعی وقتی میخوای یه بد افزار رو آنالیز کنی با میلیونها خط کد مواجهی که هر کدوم برا خودشون یه داستانی داره.
خب ببینید اینجا من یه نکته باید بگم. تصویر مشخص کرده اون تابعی که پیغام رو نشون میده، MessageBox از کتابخونه user32.dll هست. که البته اینجا خود دیباگر زحمت کشیده برا شما و اسم این تابع رو براتون نوشته.
حالا من اینجا باید وارد بشم یه کم نکته در مورد نحوه فراخونی تابع توی اسمبلی بگم که بتونیم بفهمیم چه اتفاقی داره رخ میده.
زمانی که یه تابع میخواد فراخوانی بشه، قبل از فراخوانی، آرگومانهای تابع باید ارسال بشه داخل پشته یا همون stack. که زمانی که تابع فراخوانی شد، بتونه از داخل پشته مقادیر رو برداره و عملیاتشو انجام بده. این عملیات ارسال پارامتر و آرگومان توی پشته، با دستور push اتفاق میافته و بعد از اینکه تمام پارامترها push شدند، نوبت به فراخوانی تابع میرسه که با دستور call انجام میشه.
خب ببینید الان تابع messageBox فراخوانی میشه با دستور call ولی همونطور که گفتیم، قیل از فراخوانی، پارامترها میرن داخل پشته که اگه توی عکس، قسمت پایین سمت راست رو ببینید، من دورش خط کشیدم اونجا stack هست و پارامترهای این تابع که شامل پیغام و عنوان پیغام هست، مشخصه.
خب حالا توی کد رو ببینید مشخصه که قبل از اینکه call رخ بده، ما یه سری push داریم که به ترتیب میخوایم اینارو بررسی کنیم.
اول بیاین یه کاری کنیم. فرض کنیم ما اصلا نمیدونیم این تابع MessageBox چیه. خب میریم توی اینترنت و میزنیم messagebox MSDN که بتونیم در موردش توی سایت مایکروسافت بخونیم ببینیم اصن پارامترهای این تابع چیه.
خب ببینید توی سایت MSDN که میریم به ما میگه نحوه فراخوانی این تابع به این شکله و ترتیب آرگومان هاشو به ما نشون داده. من یه نکته بگم اینجا: ببینید الان توی این تابع اولین پارامتر hwnd دومی lptext سومی lpCaption و چهارمی utype هست. این ترتیب پارامترهای تابع هست. ولی توی اسمبلی زمانی که بخوایم یه تابع رو فراخوانی کنیم باید به ترتیب از آخر به اول، پارامتر ها رو push کنیم. یعنی چی؟ یعنی اول باید utype رو push کنیم بعد lpcaption بعد lpText و دراخر hWnd
اینجا نمیخوام من وارد مبحث Calling Convention بشم ولی میتونید تمام این مطالب رو با یه سرچ بفهمید.
خب این پارامترها هرکدوم چی هستند؟ طبق توضیحی که توی MSDN داده شده:
Hwnd : یه هندل پنجره ای هست که مالکیت این پیغام رو داره و اگه این NULL باشه، یعنی پیغام بی صاحبه.
lpText: این رشته ای هست که قراره توی پیغام نمایش داده بشه. که اینجا همون پیغام “I never broke the encoding:” میشه
lpCaption: این رشته ای هست که توی عنوان پیغام نمایش داده میشه که اینجا همون “output” هست.
uType: این یه عدد هست که نوع پیغام رو مشخص میکنه. اینجا پیغام ما یه دکمه ok بیشتر نداره.
خب حالا با توجه به نکاتی که گفتم و تصویری که از کد توی دیباگر میبینید، باید متوجه بشید که اولین push، آخرین پارامتر هست یعنی uType که اینجا یه مقدار 0 داخل پشته push شده که نوع پیغام رو از نوع Ok میذاره. دومین push همون lpCaption هست که خب میبینید که یه آدرس push شده که ادرس رشته Caption هست که البته خود دیباگر اینو براتون تشخیص داده و توی سمت راست نوشته. با فلش قرمز مشخصش کردم توی عکس.
سومین push مربوط میشه به lpText که مهم ترین قسمت برا ما همینه اینجا ما میبینیم که مقدار edx داخل پشته رفته ولی قبلش یه دستور lea داریم که اومده یه آدرس رو ریخته توی edx حالا در ادامه با همین قسمت کار داریم.
و آخرین push هم مربوط به همون هندل هست که 0 داخل پشته رفته. حالا پس فهمیدیم که چه جوری یه تابع فراخوانی میشه. حالا بیایم ببینیم این رشته ای که میره برا نمایش داخل پیغام از کجا میاد؟ آیا یه رشته ثابته یا نه ؟ خب توی تصویر زیر دقت کنید که اون جایی که مشخص کردم، نشون میده که آدرس بافر EBP-84 توی EDX رفته و بعدش edx به عنوان پارامتر سوم push شده.
اینجا یه نکته قابل توجه هست. من از اینجا به بعد اسم این آدرس EBP-84 رو میذارم “بافر”
ببینید قبل از اینکه این بافر وارد پشته بشه برای تابع MessageBox ، بالاتر داریم میبینیم توی قسمت شماره 1 که وارد پشته شده به عنوان یه پارامتر برای یه تابع دیگه.
خب اینجا قضیه یه کم مشکوک میشه چرا؟؟ چونکه این بافر رفته تو اون تابع بالایی و بعد از اون تابع روش یه کارایی انجام داده و بعدش اومده توی MessageBox نمایش داده شده. خب حالا ما میدونیم که وقتی این بافر میاد توی تابع MessageBox مقدارش برابر “I never broke encoding” هست. اما سوال اینجا پیش میاد که آیا این بافر تو همون قسمت 1 هم همین مقدارو داشته یا اینکه بعد از وارد شدن به اون تابع اولی، مقدار گرفته؟ خب برا پاسخ به این سوال میریم و برنامه رو توی دیباگر دوباره اجرا میکنیم و خط به خط میریم جلو تا برسیم به اون خط شماره 1 تا ببینیم داخل بافر چی هست.
خب برنامه رو دوباره توی دیباگر اجرا کردیم و F8 زدیم تا رسیدیم به اون خطی که مورد نظر بود. من برا اینکه بتونم ببینم توی یه بافر یا آدرس حافظه چی هست میتونم به راحتی روی اون خط کلیک راست کنم و از قسمت Fllow in Dump گزینه memory address رو انتخاب کنم. این میاد و این آدرس بافر مارو توی پایین سمت چپ که بهش میگن پنجره memory dump نشون میده. خب وقتی اینو زدیم و اون پایین رو نگاه میکنیم میبینیم که همش یه مشت صفره. یعنی چی؟ یعنی بافر خالیه.
پس اینجا به جواب سوالی که بالا مطرح کردیم میرسیم. این بافر اول خالیه و مقداری توش نیس وقتی که وارد این تابع شماره 1 میشه ، توسط این تابع پر میشه. برا اینکه مطمئن بشیم از این قضیه، F8 میزنیم تا برسیم به خط بعد از Call که تابع اجرا بشه و همین پنجره پایین سمت چپ یا همون memory dump رو نگاه میکنیم.
خب همونطور که میبینید دیگه خبری از 0 نیست و این بافر با همون جمله معروف پر شده. پس این تابع شماره 1 داره یه کارایی انجام میده. حالا برا اینکه بفهمیم این تابع چیکار میکنه باید داخلش بریم ببینیم چه خبره. چونکه تا اینجا ما به چیز خاصی نرسیدیم هنوز. پس برنامه رو دوباره توی دیباگر اجرا میکنیم، F8 میزنیم تا ابتدای دستور call اول. بعد از اون برا اینکه ببینیم توی تابع چه خبره باید از step into استفاده کنیم که بالاتر توضیح دادم. برا این کار F7 میزنیم و میریم داخل اون تابع. وقتی وارد تابع میشیم، میبینیم که یه تابع دیگه هم Call شده دوباره F7 میزنیم و میریم داخل اون تابع و اینجا با همچین کدی مواجه میشیم:
خب ببینید اینجا ما با کلی دستور SHL و OR و AND و اینها مواجه هستیم. هرجا این دستوراتو توی یه تابع به تعداد زیاد دیدید بدونید که اونجا داره یه رمزگشایی، رمزنگاری، یا encoding و decoding انجام میشه. خب آیا ما باید بیایم و الگوریتم رمزگشایی رو پیدا کنیم و بعد بفهمیم چه جوری رمز گشایی انجام میشه ؟؟ این کار خیلی زمان بر هست و اینکه اگه برگردیم به اون چیزی که توی فایل Message گفته شده بود میفهمیم که قطعا راه ساده تری هم هست.
خب با توجه به اینکه الان ما فهمیدیم این تابع کارش رمزگشایی هست، من اسمش رو از این به بعد میذارم تابع رمزگشایی.
خب بیایم یه بار چیزایی که داریم رو باهم بررسی کنیم: ما فهمیدیم یه بافر خالی میره داخل این تابع رمز گشایی، و بعدش با اون جمله معروف پر میشه و میاد بیرون. خب اگه این تابع رمزگشایی هست، چی رو رمز گشایی میکنه؟ چه چیزی رمزگشایی میشه که بعدش تبدیل میشه به جمله I never broke ؟؟
خب برگردیم به اول برنامه. همونطور که میبینید قبل از اینکه این تابع رمزگشا اجرا بشه چندتاpush انجام شده. ولی این تابع رو دیگه دیباگر برا ما نتونسته شناسایی کنه چرا؟؟ چونکه این یه تابعی هست که خود برنامه نویس نوشته. دییاگر فقط میتونه توابعی که از API های ویندوز هست رو شناسایی کنه. خب حالا ما این تابع رو خودمون شناسایی کردیم و اسم هم براش گذاشتیم به نام رمزگشا.
حالا پارامترهاشو ببینیم:
خب اولین چیزی وارد پشته شده به عنوان ارگومان این تابع یه عدد hex هست که مقدارش 1C هست که توی مبنای ۱۰ میشه ۲۸ اینو تو ذهنتون نگه دارید.
خب بعد از اون هم میبینیم که یه آدرسی push شده و در نهایت هم همون بافر خودمون رفته تو پشته.
خب این آدرسه که push شده رو اگه داخل memory dump ببینیم، میفهمیم که یه سری کارکترهای نامفهومی داخلشه.
اینجا میشه حدس زد که این همون محتویاتی هست که encrypt شده و وارد این تابع میشه و رمزگشایی میشه و محتویات رمزگشایی شدش میره داخل بافر. حالا میتونیم بیایم یه بار دیگه بررسی کنیم چیزایی که به دست اوردیم تا الان:
– این تابع یه عدد بهش ارسال میشه که عدد ۲۸ هست.
– این تابع یه بافر خالی برای ذخیره کردن محتوای رمزگشایی شده نیاز داره.
– و اینکه این تابع یه آدرسی بهش پاس داده میشه که داخل اون آدرس محتویات Encrypt شده وجود داره که این تابع این محتویات رو decrypt میکنه برامون.
حالا چیکار کنیم؟ بازم چیز خاص به درد بخوری برامون نداره. بیاین یه کاری کنیم: ما الان یه عدد ۲۸ اینجا داریم که رفته تو این تابع؟ سوال اینجاس: چرا ۲۸؟؟ چرا یه عدد دیگه پاس ندادن به تابع؟
خب بزارید یه کم از قوه تخیلمون استفاده کنیم ببینیم این دلیلش چی هست:
اولین حدسی که به ذهن من میرسه اینه که ممکنه این عدد طول رشته Encrypt شده باشه. مثلا شما توی تابع strncpy توی زبان c یه عدد برا این تابع ارسال میکنید که به تابع میگه که برام تا اندازه این عدد کپی کن. خب اینجا هم ممکنه این عدد 28 برا همین باشه که به تابع رمزگشا بگه که طول رشته Encrypt شده 28 کارکتر هست.
خب اگه فرض رو بر این بزاریم که این حدسمون درسته، برای صحتش باید یه کاری کنیم، اونم اینه که طول اون پیغام داخل messageBox رو پیدا کنیم.
خب برنامه رو یه بار دیگه اجرا کنیم پیغام رو ببینیم، طولش رو بشماریم کارکتر به کارکتر:
“I never broke the encoding:”
طول پیغام تو نگاه اول ۲۷ تا هست ولی احتمالا یه فاصله ای چیزی تهش یا ابتداش هست که با اون میشه ۲۸ تا کارکتر. خب قضیه بیشتر جالب شد.
خب حالا فهمیدیم که حدس ما درسته. این 28 همون طول متن رمز شده هست، ولی بیایم یه نگاه دیگه بندازیم به memory dump مربوط به اون رشته ای که Encrypt شده بود.
خب اینجا اگه بشماریم تعداد کارکترها رو میفهمیم که طول متن Encrypt شده بیشتر از ۲۸ تا هست ولی به تابع گفته شده که تو فقط ۲۸ تاشو رمزگشایی کن. پس بقیش چی هست؟ خب بزارید تست کنیم. حالا فرض کنید ما مثلا عدد ۳۰ رو پاس بدیم به تابع. ببینیم چه اتفاقی میافته:
برا این کار باید برنامه رو دستکاری کنیم. به چه صورت؟ به این صورت که برنامه رو توی دیباگر اجرا میکنیم و قبل از اینکه دکمه play رو بزنیم مقدار push مربوط به عدد 1c رو تغییر میدیم. خب عدد ۳۰ توی مبنای hex میشه 1E خب اینو میزاریم جای 1C روی همون push مربوط به 1c دابل کلیک کنید تا پنجره modify براتون باز بشه.
خب بعد دکمه Assemble رو میزنیم و میبینیم که تغییر کرد. حالا برنامه رو با دکمه F9 یا play اجرا میکنیم. خب بعد از اجرا میبینیم که حدسمون درست بوده و دوتا کارکتر به پیغام اضافه شده. J خب اینجا میفهمیم که این قصه سر دراز دارد و طول این رشته بیشتر از این حرفاست.
خب حالا ما باید بفهمیم که طول مناسب چه عددی هست. یه راه اینه که همینجوری تست کنیم عددهای بیشتر رو ولی این کار بمونه برا خودتون دیگه.
طول نهایی جمله عدد 7f یا ۱۲۷ هست که وقتی میذاریمش رمز ورود به مرحله بعد هویدا میشه.
خب اینجا هم پسورد ورود به مرحله بعد رو پیدا کردیم.
یه راه حل دیگه هم وجود داره برا این مساله که خب چند نفر از این راه رفتن و اونم اینه که شما بیاین و الگوریتم رمزگشایی رو پیدا کنید و اون رشته Encrypt شده رو باهاش رمزگشایی کنی و به رمز برسی. این راه خیلی زمان بر هست چون علاوه بر اینکه وقت زیادی برای کشف الگوریتم میگیره، شما باید دست به کد هم بشید و کد بنویسید.
خب منتظر مراحل بعدی باشید که توی هفته جدید میزارم.
سوالی اگه بود twitter و یا گروه عمومی آفسک میتونید بپرسید.