# درباره‌ی چالش‌های CTF **تعریف کلی:** CTF (Capture The Flag) (به فارسی: **پرچم را تصاحب کن**) یک نوع مسابقه امنیت سایبری است که در آن شرکت‌کنندگان (تیم‌ها یا افراد) مهارت‌های خود را در یافتن و بهره‌برداری از آسیب‌پذیری‌ها، تحلیل سیستم‌ها، رمزگشایی و حل مسائل پیچیده امنیت اطلاعات به چالش می‌کشند. 💻🕵️‍♀️ ## هدف اصلی: هدف اصلی در CTF پیدا کردن "**فلگ**" (Flag) است. فلگ معمولاً یک رشته متنی خاص (مانند `flag{this_is_your_flag}`) است که در یک مکان پنهان (مثلاً در یک فایل، دیتابیس، یا خروجی یک برنامه آسیب‌پذیر) قرار دارد. با یافتن و وارد کردن این فلگ در سیستم مسابقه، تیم امتیاز کسب می‌کند. ## دو نوع رایج CTF: * **رایج‌ترین نوع: Jeopardy (ژئوپاردی)** چالش‌ها به صورت مستقل و در دسته‌بندی‌های مختلف (مانند مهندسی معکوس، رمزنگاری، وب، فارنزیک) با امتیازات متفاوت ارائه می‌شوند. شرکت‌کنندگان هر چالشی را که می‌خواهند انتخاب کرده و فلگ آن را پیدا می‌کنند. * **پیچیده‌تر و دینامیک‌تر: Attack-Defense (حمله-دفاع)** هر تیم یک سرور (یا مجموعه‌ای از سرویس‌ها) را در اختیار دارد که باید از آن در برابر حملات تیم‌های دیگر دفاع کند و همزمان به سرورهای حریف حمله کرده و فلگ‌های آن‌ها را به دست آورد. این نوع CTF مهارت‌های دفاعی و تهاجمی را همزمان می‌سنجد. ## چرا CTF؟ * یادگیری و تمرین مهارت‌های **امنیت سایبری** در محیطی عملی و کنترل‌شده. * شناسایی و جذب استعدادها در حوزه امنیت. * افزایش آگاهی نسبت به آسیب‌پذیری‌های رایج و تکنیک‌های نفوذ. به طور خلاصه، CTF یک ورزش ذهنی هیجان‌انگیز در دنیای سایبری است که به شما کمک می‌کند مهارت‌های عملی خود را در امنیت اطلاعات بهبود ببخشید. 🧠🛡️ ## دسته‌بندی‌های رایج چالش‌ها در CTF (سبک Jeopardy): در مسابقات CTF با فرمت Jeopardy، چالش‌ها معمولاً به دسته‌بندی‌های مختلفی تقسیم می‌شوند تا تیم‌ها بتوانند بر اساس تخصص خود، چالش‌ها را انتخاب کنند. این دسته‌بندی‌ها می‌توانند کمی متفاوت باشند، اما رایج‌ترین آن‌ها عبارتند از: * **مهندسی معکوس (Reversing):** بررسی و تحلیل کدهای کامپایل‌شده (مانند فایل‌های اجرایی EXE، ELF) برای درک عملکرد آن‌ها، کشف آسیب‌پذیری‌ها یا استخراج اطلاعات پنهان (مثل رمزها یا فلگ‌ها). ابزارهای مورد استفاده: دی‌اسامبلرها (مثل IDA Pro، Ghidra، Radare2)، دی‌باگرها (مثل GDB, x64dbg). * **رمزنگاری (Cryptography):** شکستن الگوریتم‌های رمزنگاری، تحلیل پروتکل‌های رمزنگاری، یا پیدا کردن ضعف‌ها در پیاده‌سازی آن‌ها برای رمزگشایی داده‌ها و یافتن فلگ. شامل تحلیل الگوهای رمز، کلیدهای ضعیف، یا اشکالات منطقی در سیستم‌های رمزنگاری. * **وب (Web Exploitation):** پیدا کردن و بهره‌برداری از آسیب‌پذیری‌های موجود در برنامه‌های تحت وب (مانند SQL Injection, Cross-Site Scripting (XSS), File Inclusion, Authentication Bypass) برای دسترسی به اطلاعات یا اجرای کد. * **فارنزیک (Forensics):** تحلیل شواهد دیجیتال (مثل تصاویر دیسک، فایل‌های حافظه، ترافیک شبکه، فایل‌های سیستمی) برای بازسازی وقایع، شناسایی فعالیت‌های مخرب یا استخراج اطلاعات پنهان. * **باینری اکسپلویت یا PWN (Binary Exploitation / Pwning):** پیدا کردن و بهره‌برداری از آسیب‌پذیری‌ها در برنامه‌های بومی (native binaries) مانند سرریز بافر (Buffer Overflow)، فرمت استرینگ (Format String) یا use-after-free برای کنترل جریان اجرای برنامه و دستیابی به Shell یا اجرای کد دلخواه. * **استگانوگرافی (Steganography):** پیدا کردن اطلاعات پنهان‌شده در فایل‌های رسانه‌ای (تصاویر، صدا، ویدئو) یا سایر فرمت‌های فایل، جایی که داده‌ها به گونه‌ای مخفی شده‌اند که حضور آن‌ها آشکار نباشد. * **عمومی یا متفرقه (General Skills / Misc):** شامل طیف گسترده‌ای از چالش‌هاست که در دسته‌بندی‌های دیگر قرار نمی‌گیرند، مانند چالش‌های مربوط به لینوکس، اسکریپت‌نویسی، اوزینت (OSINT)، یا حل معماهای منطقی. * **شبکه (Networking):** تحلیل ترافیک شبکه (بسته‌های کپچر شده با Wireshark)، پروتکل‌های شبکه، و پیدا کردن آسیب‌پذیری‌ها یا اطلاعات پنهان در ارتباطات شبکه. * **سیستم عامل‌ها/کانفینگ (OS/Config):** چالش‌های مربوط به پیکربندی‌های سیستم عامل، دسترسی به فایل‌ها، مجوزها، یا بهره‌برداری از تنظیمات نادرست در سیستم‌ها. ## توضیحات چالش: Bamboo Fox: Better Than Assembly این چالش 500 امتیاز داشت و در دسته‌بندی **مهندسی معکوس (Reversing)** در سبک CTF از نوع Jeopardy قرار می‌گرفت. **نکته:** منظور از Jeopardy در مسابقات CTF (Capture The Flag)، یک قالب (format) خاص برای ارائه و حل چالش‌هاست. یا به طور خلاصه، وقتی می‌گوییم یک چالش در سبک CTF از نوع Jeopardy است، یعنی شما با یک سوال یا مسئله مستقل روبرو هستید که باید آن را حل کنید تا فلگ مربوطه را پیدا کرده و امتیاز کسب کنید. # مسیر حل چالش ## گام اول: اسکن کردن فایل ابزار ClamAV (Clam AntiVirus) یک موتور آنتی‌ویروس متن‌باز و رایگان است که به طور گسترده برای شناسایی تروجان‌ها، ویروس‌ها، بدافزارها و سایر تهدیدات مخرب استفاده می‌شود. این نرم‌افزار به ویژه در سرورهای ایمیل برای اسکن فایل‌های ضمیمه و جلوگیری از ورود بدافزارها از طریق ایمیل محبوبیت دارد، اما می‌توان از آن برای اسکن فایل‌ها و دایرکتوری‌ها در سیستم‌های لینوکس، یونیکس و ویندوز نیز بهره برد. ابزار خط فرمان اصلی برای اسکن فایل‌ها با ClamAV، دستور `clamscan` است که به کاربران اجازه می‌دهد مسیرهای مشخصی را برای یافتن امضاهای بدافزار (که از پایگاه داده ویروس ClamAV به‌روزرسانی می‌شوند) اسکن کنند. این ابزار به دلیل ماهیت متن‌باز بودن و قابلیت سفارشی‌سازی بالا، گزینه‌ای قدرتمند و انعطاف‌پذیر برای افزودن قابلیت‌های اسکن آنتی‌ویروس به اسکریپت‌ها و سیستم‌های خودکار است. ```sh sudo clamscan --infected --recursive sudo apt install clamav clamav-daemon clamav-freshclam sudo freshclam URL: https://docs.clamav.net/manual/Usage/Scanning.html URL: https://x.com/pcaversaccio/status/1941114624197231092 ``` ## گام دوم: شناسایی نوع و ماهیت فایل‌ها ### دستور ExifTool دستور ExifTool یک ابزار خط فرمان رایگان و متن‌باز و یک کتابخانه پِرل (Perl library) قدرتمند است که برای خواندن، نوشتن و ویرایش فراداده (metadata) در طیف وسیعی از فرمت‌های فایل، از جمله تصاویر (EXIF, IPTC, XMP)، ویدئوها، فایل‌های صوتی و اسناد PDF، استفاده می‌شود. این ابزار قادر است تقریباً تمام تگ‌های فراداده استاندارد و سفارشی را استخراج و دستکاری کند، که آن را برای عکاسان، محققان پزشکی قانونی دیجیتال، توسعه‌دهندگان و هر کسی که نیاز به مدیریت دقیق اطلاعات جاسازی شده در فایل‌ها دارد، بی‌اندازه ارزشمند می‌سازد. ExifTool به دلیل پشتیبانی گسترده‌اش از انواع تگ‌ها و فرمت‌ها، قابلیت‌های ویرایش دسته‌ای، و توانایی حفظ یکپارچگی داده‌ها حتی پس از تغییر فراداده، به عنوان یک استاندارد صنعتی شناخته می‌شود. URL: https://exiftool.org ```sh sudo apt install libimage-exiftool-perl ``` ### دستور File دستور `file` یک ابزار استاندارد و قدرتمند در سیستم‌عامل‌های شبه‌یونیکس (مانند لینوکس و macOS) است که برای شناسایی نوع محتوای یک فایل به کار می‌رود. برخلاف بسیاری از دستورات که نوع فایل را صرفاً بر اساس پسوند آن حدس می‌زنند، `file` با بررسی جادویی (magic numbers) موجود در ابتدای فایل‌ها، ساختار داخلی و محتوای واقعی آن‌ها را تحلیل می‌کند. این قابلیت به آن اجازه می‌دهد تا حتی فایل‌هایی را که پسوند اشتباه دارند یا اصلاً پسوندی ندارند، به درستی تشخیص دهد؛ مثلاً می‌تواند یک فایل متنی ساده، یک فایل اجرایی باینری (مانند ELF، Mach-O یا PE)، یک تصویر (JPEG، PNG)، یک آرشیو فشرده (ZIP، GZ)، یا حتی یک سند Word را شناسایی کند. این دستور برای مهندسی معکوس، بررسی امنیتی فایل‌ها، یا صرفاً برای درک اینکه یک فایل ناشناخته واقعاً چیست، بسیار مفید است. ```sh file task.ll sudo apt install file ``` ### وب‌سایت FileInfo.com وب‌سایت FileInfo.com یک دایرکتوری آنلاین بزرگ از پسوندهای فایل است که به شما امکان می‌دهد با وارد کردن پسوند یک فایل، اطلاعات جامعی درباره آن کسب کنید. این وب‌سایت توضیحات مربوط به نوع فایل، دسته‌بندی آن (مانند فایل ویدئویی یا سند)، برنامه‌های نرم‌افزاری مرتبط که می‌توانند آن را باز کنند و اطلاعاتی درباره توسعه‌دهنده فرمت را ارائه می‌دهد، که آن را به ابزاری مفید برای شناسایی فایل‌های ناشناخته تبدیل می‌کند. ## گام سوم: کامپایل و اجرای فایل نکته: پسوند فایل .ll عمدتاً به دو منظور استفاده می‌شود: رایج‌ترین آن، فایل‌های پیش‌نمایش تولید شده توسط نرم‌افزار Combit List & Label است که برای گزارش‌گیری کاربرد دارد. اما در حوزه کامپایلرها، به‌ویژه در پروژه LLVM، این پسوند به فایل‌های سورس نمایش میانی (Intermediate Representation - IR) اشاره دارد که یک فرمت کد سطح پایین و قابل خواندن توسط انسان است و نقش واسطه‌ای بین کد منبع و کد ماشین را ایفا می‌کند تا بهینه‌سازی و تولید کد برای معماری‌های مختلف را تسهیل کند. به‌ندرت نیز ممکن است به فایل‌های کد منبع Lex اشاره داشته باشد. LLVM Bitcode مانند یک زبان مشترک جهانی برای کامپایلرها است کامپایل: (کلنگ) Clang یک کامپایلر فرانت‌اند (frontend) برای زبان‌های برنامه‌نویسی C، C++، Objective-C و Objective-C++ است. این کامپایلر بخشی از پروژه بزرگتر LLVM (Low Level Virtual Machine) است و به دلیل سرعت بالا، پیام‌های خطای خوانا، و پشتیبانی قوی از استانداردهای جدید زبان‌ها، بسیار محبوب شده است. ```sh sudo apt install clang URL: https://github.com/llvm/llvm-project ``` ```sh clang task.ll -mllvm -W -g -W1,-pie -o task.out ``` اجرا: ```sh $ ./task.out Only the chosen one will know what the flag is! Are you the chosen one? flag: ``` خروجی: ```sh flag: Hi 😠😡😠😡😠😡 You are not the chosen one! 😡😠😡😠😡😠 ``` ## گام چهارم: تحلیل باینری ### ابزار Cutter یک پلتفرم مهندسی معکوس (Reverse Engineering) رایگان و متن‌باز است که بر پایه موتور Rizin ساخته شده است. هدف اصلی Cutter ارائه یک محیط پیشرفته و قابل شخصی‌سازی برای مهندسی معکوس است، ضمن اینکه تجربه کاربری را نیز در نظر می‌گیرد. این ابزار توسط مهندسان معکوس برای مهندسان معکوس توسعه یافته است. Cutter ابزاری چندپلتفرمی است و برای سیستم‌عامل‌های اصلی مانند لینوکس، macOS و ویندوز در دسترس می‌باشد. از ویژگی‌های برجسته آن می‌توان به رابط کاربری گرافیکی (GUI) مبتنی بر Qt/C++، ادغام بومی با دی‌کامپایلر Ghidra، نماهای مختلف (مانند Graph View و Hex Editor)، قابلیت اسکریپت‌نویسی با پایتون، پلاگین‌ها و پشتیبانی از دیباگر (Debugger) اشاره کرد. نحوه اجرا برنامه: ```sh chmod +x Cutter*.AppImage; ./Cutter*.AppImage URL: https://cutter.re/ URL: https://github.com/rizinorg/cutter ``` خروجی دیکامپایل فایل task.out ```sh IDA Pro: int __cdecl main(int argc, const char **argv, const char **envp) { // متغیرهای محلی (Local Variables) // اینها معمولاً توسط دی‌کامپایلر به صورت خودکار نامگذاری می‌شوند (v4, v5, etc.) // و سپس تحلیلگر می‌تواند نام‌های بامعنی‌تری به آنها بدهد. char v4; // [rsp+14h] [rbp-94h] char v5; // [rsp+34h] [rbp-74h] size_t v6; // [rsp+40h] [rbp-68h] // احتمالاً برای ذخیره طول رشته s استفاده می‌شود. int j; // [rsp+58h] [rbp-50h] // شمارنده حلقه for در بلاک 'else' int i; // [rsp+5Ch] [rbp-4Ch] // شمارنده حلقه for در بلاک 'if' char s[68]; // [rsp+60h] [rbp-48h] BYREF // آرایه‌ای برای ذخیره ورودی کاربر (68 بایت) int v10; // [rsp+A4h] [rbp-4h] // متغیری برای ذخیره مقدار بازگشتی تابع (معمولاً 0 برای موفقیت، 1 برای خطا) v10 = 0; // مقدار بازگشتی اولیه را 0 (موفقیت) تنظیم می‌کند. // چاپ پیام‌های خوش‌آمدگویی و درخواست فلگ از کاربر printf("Only the chosen one will know what the flag is!\n"); printf("Are you the chosen one?\n"); printf("flag: "); // دریافت ورودی کاربر // %64s به این معنی است که حداکثر 64 کاراکتر را در بافر 's' می‌خواند (برای جلوگیری از Buffer Overflow) __isoc99_scanf("%64s", s); // محاسبه طول رشته ورودی کاربر v6 = strlen(s); // شرط اصلی برنامه: مقایسه طول ورودی کاربر با طول یک رشته پنهان (what) if ( v6 == strlen(&what) ) // اگر طول ورودی کاربر با طول "what" برابر باشد { // اگر تابع 'check(s)' (که احتمالاً یک تابع اعتبارسنجی است) مقدار غیرصفر (true) برگرداند if ( (unsigned int)check(s) ) { // حلقه اول: در صورت موفقیت 'check(s)' // این حلقه یک عملیات XOR روی هر کاراکتر ورودی 's' با کاراکتر متناظر از 'secret' انجام می‌دهد. // نتیجه به صورت کاراکتر به کاراکتر در خود 's' ذخیره می‌شود. for ( i = 0; i < strlen(s); ++i ) { v5 = s[i]; // کاراکتر فعلی از ورودی کاربر s[i] = secret[i % strlen(secret)] ^ v5; // XOR با 'secret' (به صورت دوره‌ای) } } else // اگر تابع 'check(s)' مقدار صفر (false) برگرداند { // حلقه دوم: در صورت عدم موفقیت 'check(s)' // این حلقه یک عملیات XOR روی هر کاراکتر از 'flag' (که به نظر می‌رسد فلگ واقعی است) // با کاراکتر متناظر از 'secret' انجام می‌دهد. // نتیجه به صورت کاراکتر به کاراکتر در 's' ذخیره می‌شود. for ( j = 0; j < strlen(s); ++j ) // s[j] اینجا همان s ورودی کاربر است { v4 = flag[j]; // کاراکتر فعلی از رشته 'flag' s[j] = secret[j % strlen(secret)] ^ v4; // XOR با 'secret' (به صورت دوره‌ای) } } // چاپ نتیجه عملیات XOR (که در 's' ذخیره شده) با استفاده از فرمت مشخص شده printf(format, s); v10 = 0; // تنظیم مقدار بازگشتی به 0 (موفقیت) } else // اگر طول ورودی کاربر با طول "what" برابر نباشد { // چاپ پیام خطا (asc_40205A احتمالا آدرس یک رشته خطاست) printf(asc_40205A); v10 = 1; // تنظیم مقدار بازگشتی به 1 (خطا) } return v10; // بازگرداندن وضعیت نهایی برنامه } ``` ```sh Cutter: /* jsdec pseudo code output */ /* /home/task.out @ 0x401230 */ // مسیر و آدرس شروع تابع main #include // اضافه کردن هدر برای انواع داده استاندارد int32_t main (int64_t arg_60h) { // تعریف تابع main، arg_60h احتمالاً نشان‌دهنده آرگومان‌های ورودی تابع است // متغیرهای محلی // jsdec (دی‌کامپایلر Cutter) نام‌های متغیرها را بر اساس آفست آنها از RSP (Stack Pointer) تعیین می‌کند. int64_t var_a8h; size_t * var_a0h; int64_t var_94h; int64_t var_90h; size_t var_88h; size_t * var_80h; int64_t var_74h; int64_t var_70h; size_t var_68h; // معادل v6 در IDA: برای ذخیره طول رشته int32_t var_60h; // معادل متغیرهای موقت برای ذخیره خروجی توابع (مثل scanf, printf) int32_t var_5ch; int32_t var_58h; int32_t var_54h; int64_t var_50h; // معادل j در IDA: شمارنده حلقه دوم int64_t var_4ch; // معادل i در IDA: شمارنده حلقه اول const char * s; // معادل char s[68] در IDA: بافر ورودی کاربر. Cutter آن را به صورت اشاره‌گر به char نشان می‌دهد. int64_t var_4h; // معادل v10 در IDA: متغیر بازگشتی تابع var_4h = 0; // مقدار بازگشتی اولیه را 0 (موفقیت) تنظیم می‌کند. // چاپ پیام‌ها با استفاده از رجیستر RDI برای نگهداری آدرس رشته rdi = "Only the chosen one will know what the flag is!\n"; // RDI رجیستر اولین آرگومان در تابع فراخوانی است. al = 0; // AL رجیستر برای تعداد آرگومان‌های floating-point در x64 (اینجا 0 است) eax = printf (rdi); // فراخوانی printf و ذخیره خروجی در EAX rdi = "Are you the chosen one?\n"; var_54h = eax; // ذخیره موقت مقدار EAX al = 0; eax = printf (rdi); rdi = "flag: "; var_58h = eax; al = 0; eax = printf (rdi); // دریافت ورودی کاربر // RSI برای آرگومان دوم (%64s) و RDI برای آرگومان اول (s) در scanf rsi = &s; // آدرس بافر 's' rdi = "%64s"; // فرمت ورودی var_5ch = eax; al = 0; eax = isoc99_scanf (); // فراخوانی scanf rdi = &s; var_60h = eax; // محاسبه طول رشته ورودی کاربر rax = strlen (); // فراخوانی strlen روی 's' (آرگومان RDI) edi = what; // آرگومان RDI برای strlen دوم (رشته 'what') var_68h = rax; // ذخیره طول 's' در var_68h (معادل v6 در IDA) rax = strlen (); // فراخوانی strlen روی 'what' (آرگومان EDI) rcx = var_68h; // rcx حاوی طول 's' است if (rcx != rax) { // مقایسه طول 's' با طول 'what' rdi = data.0040205a; // آدرس رشته خطا (معادل asc_40205A در IDA) al = 0; printf (rdi); // چاپ پیام خطا var_4h = 1; // تنظیم مقدار بازگشتی به 1 goto label_0; // پرش به انتهای برنامه } // اگر طول‌ها برابر باشند، تابع check(s) فراخوانی می‌شود rdi = &s; // آرگومان تابع check eax = check (); // فراخوانی check() if (eax == 0) { // اگر check() مقدار 0 (false) برگرداند (معادل else در IDA) goto label_1; // پرش به بلوک مربوط به حالت 'check' ناموفق } // بلوک کد در صورت موفقیت check() (معادل if در IDA) var_4ch = 0; // شمارنده حلقه اول (معادل i در IDA) do { // شروع حلقه for rdi = &s; rax = (int64_t) var_4ch; var_70h = rax; rax = strlen (); // محاسبه strlen(s) در هر تکرار rcx = var_70h; if (rcx >= rax) { // شرط خروج از حلقه (i < strlen(s)) goto label_2; // پرش به بعد از حلقه } rax = (int64_t) var_4ch; ecx = *((rsp + rax + 0x60)); // دسترسی به s[i] (rsp+0x60 آدرس شروع بافر s است) var_74h = ecx; // ذخیره s[i] rax = (int64_t) var_4ch; edi = "B\n|_"; // این رشته یک artifact از دی‌کامپایلر است و مربوط به کد واقعی نیست. var_80h = rax; rax = strlen (); // محاسبه strlen(secret) rdx = var_80h; var_88h = rax; rax = rdx; ecx = 0; edx = ecx; rsi = var_88h; rax = rdx:rax / rsi; // محاسبه i / strlen(secret) (در واقع همان i % strlen(secret) است) rdx = rdx:rax % rsi; // محاسبه i % strlen(secret) ecx = *((rdx + secret)); // دسترسی به secret[i % strlen(secret)] r8d = var_74h; // r8d حاوی s[i] است r8d ^= ecx; // عملیات XOR: s[i] ^ secret[i % strlen(secret)] rdx = (int64_t) var_4ch; *((rsp + rdx + 0x60)) = r8b; // ذخیره نتیجه در s[i] eax = var_4ch; eax++; // افزایش i var_4ch = eax; } while (1); // حلقه بی‌پایان تا زمانی که به 'goto label_2' برسد. label_2: rsi = &s; rdi = format; // آدرس رشته فرمت برای printf al = 0; printf (rdi); // چاپ نتیجه goto label_3; // پرش به انتهای موفقیت‌آمیز برنامه label_1: // بلوک کد در صورت عدم موفقیت check() (معادل else در IDA) var_50h = 0; // شمارنده حلقه دوم (معادل j در IDA) do { // شروع حلقه for rdi = &s; rax = (int64_t) var_50h; var_90h = rax; rax = strlen (); // محاسبه strlen(s) در هر تکرار rcx = var_90h; if (rcx >= rax) { // شرط خروج از حلقه (j < strlen(s)) goto label_4; // پرش به بعد از حلقه } rax = (int64_t) arg_60h; // arg_60h در اینجا احتمالاً base address برنامه یا یک متغیر است. ecx = *((rax + flag)); // دسترسی به flag[j]. 'flag' باید آدرس رشته فلگ باشد. rax = (int64_t) var_50h; edi = "B\n|_"; // artifact دی‌کامپایلر var_94h = ecx; // ذخیره flag[j] var_a0h = rax; rax = strlen (); // محاسبه strlen(secret) rdx = var_a0h; *(rsp) = rax; rax = rdx; ecx = 0; edx = ecx; rsi = *(rsp); rax = rdx:rax / rsi; // محاسبه j / strlen(secret) rdx = rdx:rax % rsi; // محاسبه j % strlen(secret) ecx = *((rdx + secret)); // دسترسی به secret[j % strlen(secret)] r8d = var_94h; // r8d حاوی flag[j] است r8d ^= ecx; // عملیات XOR: flag[j] ^ secret[j % strlen(secret)] rdx = (int64_t) var_50h; *((rsp + rdx + 0x60)) = r8b; // ذخیره نتیجه در s[j] eax = var_50h; eax++; // افزایش j var_50h = eax; } while (1); // حلقه بی‌پایان label_4: rsi = &s; rdi = format; // آدرس رشته فرمت برای printf al = 0; printf (rdi); // چاپ نتیجه label_3: var_4h = 0; // تنظیم مقدار بازگشتی به 0 (موفقیت) label_0: eax = 0; // این خط نیز مقدار بازگشتی را 0 تنظیم می‌کند return rax; // بازگرداندن وضعیت نهایی } ``` ## گام پنجم: منطق برنامه ### منطق کلی برنامه (از هر دو کد دیکامپایل شده): - برنامه سه پیام خوش‌آمدگویی و سپس flag: را چاپ می‌کند. - از کاربر یک رشته (احتمالاً فلگ) را با scanf می‌خواند و در بافر s ذخیره می‌کند (حداکثر ۶۴ کاراکتر). شرط اول: طول رشته ورودی کاربر (s) را با طول یک رشته دیگر به نام what مقایسه می‌کند. اگر طول‌ها برابر نباشند، پیام خطا (asc_40205A یا data.0040205a) را چاپ کرده و برنامه با کد خطا 1 خارج می‌شود. شرط دوم (در صورت برابری طول‌ها): تابعی به نام check(s) را با ورودی کاربر (s) فراخوانی می‌کند. حالت A (اگر check(s) موفق باشد): یک حلقه for اجرا می‌شود. در این حلقه، هر کاراکتر از ورودی کاربر (s[i]) با کاراکتر متناظر از رشته secret (به صورت دوره‌ای i % strlen(secret)) عملگر XOR می‌شود. نتیجه در همان بافر s ذخیره می‌شود. حالت B (اگر check(s) ناموفق باشد): یک حلقه for دیگر اجرا می‌شود. در این حلقه، هر کاراکتر از رشته flag (که احتمالاً فلگ صحیح است) با کاراکتر متناظر از رشته secret (به صورت دوره‌ای j % strlen(secret)) عملگر XOR می‌شود. نتیجه نیز در بافر s ذخیره می‌شود. در نهایت، برنامه محتوای تغییر یافته s را با استفاده از یک رشته فرمت (format) چاپ می‌کند. هدف این چالش (CTF): پیدا کردن ورودی صحیح (s) است که باعث شود تابع check(s) موفق عمل کند (حالت A). اگر بتوانیم این ورودی را پیدا کنیم، برنامه فلگ XOR شده را چاپ می‌کند که سپس باید آن را با همان secret دوباره XOR کنیم تا فلگ اصلی را به دست آوریم.اگر نتوانیم check(s) را دور بزنیم، می‌توانیم ورودی غلطی بدهیم تا برنامه به حالت B برود و فلگ واقعی (XOR شده) را برایمان چاپ کند که سپس باید آن را دیکد کنیم. خلاصه این بخش: (برنامه یک رشته ورودی (حداکثر 64 کاراکتر) دریافت می‌کنه. اگر طول و محتوای رشته درست باشه، اون رو با یک secret (مقدار سری) XOR می‌کنه. در غیر این صورت، یک رشته دیگه (که حاوی فلگ هست) رو با همون secret XOR می‌کنه و خروجی می‌ده.) ### منطق XOR و درباره رمز نگاری XOR همانطور که می‌بینید، این (کد) با خروجی برنامه هنگام اجرا مطابقت دارد. برنامه یک رشته (حداکثر 64 کاراکتر) را می‌خواند؛ اگر طول آن با طول رشته‌ای که سپس برنامه بررسی می‌کند یکسان باشد و رشته مورد نظر از بررسی عبور کند، آن را با secret (مقدار سری) XOR می‌کند. در غیر این صورت، یک رشته دیگر (فلگ) را با secret XOR می‌کند. سپس من به سراغ پیدا کردن مقادیر برای متغیرهای مختلف رفتم: what = b"\x17/'\x17\x1DJy\x03,\x11\x1E&\x0AexjONacA-&\x01LANH'.&\x12>#'Z\x0FO\x0B%:(&HI\x0CJylL'\x1EmtdC\x00\x00\x00\x00\x00\x00\x00\x00" secret = b'B\x0A|_\x22\x06\x1Bg7#\x5CF\x0A)\x090Q8_{Y\x13\x18\x0DP\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' flag = b'\x1DU#hJ7.8\x06\x16\x03rUO=[bg9JmtGt`7U\x0BnNjD\x01\x03\x120\x19;OVIaM\x00\x08,qu#'Z\x0FO\x0B%:(&HI\x0CJylL'\x1EmtdC\x00\x00\x00\x00\x00\x00\x00\x00" # متغیر `what` را تعریف می‌کند که حاوی بایت‌های رشته ثابت `what` است. # این رشته مستقیماً از فایل باینری برنامه اصلی (که در تابع `check()` استفاده می‌شود) استخراج شده است. secret = b'B\x0A|_\x22\x06\x1Bg7#\x5CF\x0A)\x090Q8_{Y\x13\x18\x0DP\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # متغیر `secret` را تعریف می‌کند که حاوی بایت‌های کلید رمزنگاری است. # این مقدار نیز از تحلیل فایل باینری به دست آمده و برای عملیات XOR نهایی استفاده می‌شود. what = what.rstrip(b'\0') # بایت‌های NULL (`\0`) را از انتهای رشته `what` حذف می‌کند. # این کار برای اطمینان از اینکه طول دقیق داده‌ها در محاسبات بعدی استفاده شود، انجام می‌شود. secret = secret.rstrip(b'\0') # بایت‌های NULL (`\0`) را از انتهای رشته `secret` حذف می‌کند. # این کار نیز برای حفظ دقت طول رشته در عملیات XOR ضروری است. for i in range(30, 127): # این حلقه یک **بروت‌فورس (حدس زدن)** هوشمند را آغاز می‌کند. # `range(30, 127)` شامل کدهای ASCII برای کاراکترهای قابل چاپ رایج (مانند اعداد، حروف، نمادها) است. # در هر تکرار، `i` به عنوان **اولین کاراکتر احتمالی ورودی صحیح** برای تابع `check()` در نظر گرفته می‌شود. flag = [] # در هر تکرار حلقه بیرونی (`for i in ...`)، یک لیست خالی به نام `flag` ایجاد می‌شود. # این لیست `flag` در اینجا در واقع دنباله بایت‌های **ورودی صحیح برای تابع `check()`** (یعنی رشته `s` در تابع `main`) را نشان می‌دهد که ما در حال تلاش برای بازسازی آن هستیم. flag.append(i) # اولین کاراکتر حدس زده شده (`i`) به عنوان اولین بایت به لیست `flag` اضافه می‌شود. for j in range(len(what)): # این حلقه داخلی، بقیه بایت‌های دنباله ورودی صحیح (`flag` در اینجا) را بازسازی می‌کند. # این فرآیند **معکوس کردن منطق تابع `check()`** است. # از آنجایی که از تابع `check()` می‌دانیم: `(بایت_بعدی_ورودی ^ بایت_فعلی_ورودی) == what[j]` # با استفاده از خاصیت XOR (`A ^ B = C` پس `A = C ^ B`)، می‌توانیم بایت بعدی را به صورت زیر محاسبه کنیم: # `بایت_بعدی_ورودی = بایت_فعلی_ورودی ^ what[j]` flag.append(flag[j]^what[j]) # `flag[j]` (که در اینجا همان `بایت_فعلی_ورودی` است) با `what[j]` (که از رشته ثابت `what` می‌آید) XOR می‌شود. # نتیجه XOR به عنوان `بایت_بعدی_ورودی` (یعنی `flag[j+1]`) به لیست `flag` اضافه می‌شود. # این خط به صورت زنجیره‌ای بایت‌های بعدی ورودی صحیح را بر اساس بایت قبلی و مقادیر `what` بازسازی می‌کند. newFlag = "" # یک رشته خالی به نام `newFlag` ایجاد می‌شود. # این رشته برای ذخیره فلگ نهایی (که پس از رمزگشایی کاندیدای ورودی صحیح با `secret` به دست می‌آید) استفاده می‌شود. for j in range(len(what)): # این حلقه، کاندیدای ورودی صحیح بازسازی شده (که در لیست `flag` است) را با کلید `secret` XOR می‌کند. # این عملیات **شبیه‌سازی فرآیند نهایی تولید فلگ توسط برنامه اصلی** است که در صورت موفقیت `check()` انجام می‌شود. newFlag += chr(flag[j]^secret[j%len(secret)]) # `flag[j]`: بایت `j`ام از کاندیدای ورودی صحیح بازسازی شده. # `secret[j%len(secret)]`: بایت متناظر از کلید `secret` را انتخاب می‌کند (با استفاده از عملگر `%` برای تکرار چرخشی کلید). # `^`: عملیات XOR را بین دو بایت انجام می‌دهد. # `chr()`: نتیجه عددی XOR را به کاراکتر ASCII مربوطه تبدیل می‌کند. # `newFlag += ...`: کاراکتر حاصل را به انتهای رشته `newFlag` اضافه می‌کند. print(newFlag) # هر کاندیدای `newFlag` تولید شده (که بر اساس هر حدس اولیه `i` و فرآیند کامل بازسازی و XOR نهایی ایجاد شده است) در خروجی چاپ می‌شود. ``` این قطعه کد پایتون یک رویکرد بروت فورس (Brute-Force) و معکوس‌سازی الگوریتم تابع check() را برای پیدا کردن فلگ اصلی پیاده‌سازی می‌کند. هدف این کد، تولید کاندیداهای فلگ است تا یکی از آن‌ها فلگ صحیح باشد. دید کلی درباره این قطعه کد 🧩 این قطعه کد پایتون یک حمله رمزی معکوس (Cryptographic Reverse Engineering) برای یافتن فلگ اصلی است. به جای اینکه سعی کند تابع check() را با ورودی‌های تصادفی دور بزند (که ناکارآمد است)، این کد منطق check() را معکوس می‌کند. فرایند کار آن به شرح زیر است: استخراج داده‌ها: فرض بر این است که مقادیر what و secret قبلاً از باینری برنامه استخراج شده‌اند. حدس هوشمندانه (Brute-Force بر روی اولین کاراکتر): می‌دانیم که فلگ‌ها معمولاً از کاراکترهای قابل چاپ تشکیل شده‌اند. این کد با حدس زدن تمامی کاراکترهای قابل چاپ ASCII (کدهای 30 تا 126) به عنوان اولین کاراکتر فلگ صحیح (ورودی s) شروع می‌کند. بازسازی دنباله فلگ: برای هر حدس اولیه، این کد از منطق معکوس تابع check() استفاده می‌کند. از آنجایی که می‌دانیم (char_next ^ char_current) == what[j] باید درست باشد، می‌توانیم char_next را با what[j] ^ char_current محاسبه کنیم. این فرآیند به صورت زنجیره‌ای ادامه می‌یابد و تمامی کاراکترهای بعدی فلگ صحیح (یا کاندیدای فلگ) را بر اساس کاراکتر قبلی و بایت متناظر از what بازسازی می‌کند. شبیه‌سازی خروجی فلگ صحیح: پس از بازسازی کامل یک کاندیدای فلگ صحیح (flag در این کد)، این کد همان عملیات XORی را انجام می‌دهد که برنامه اصلی در صورت ورودی صحیح انجام می‌دهد (یعنی XOR کردن ورودی صحیح با secret). چاپ کاندیداها: در نهایت، برای هر حدس اولیه، یک newFlag چاپ می‌شود. تحلیلگر باید خروجی‌ها را بررسی کند و فلگی را که به نظر معنی‌دار و صحیح می‌آید، پیدا کند. این رویکرد بسیار کارآمدتر از حدس زدن کل فلگ به صورت تصادفی است، زیرا از دانش بدست‌آمده از تحلیل تابع check() برای محدود کردن فضای جستجو و بازسازی هوشمندانه فلگ استفاده می‌کند. ### اسکریپت چهارم: ```python what = b"\x17/'\x17\x1DJy\x03,\x11\x1E&\x0AexjONacA-&\x01LANH'.&\x12>#'Z\x0FO\x0B%:(&HI\x0CJylL'\x1EmtdC\x00\x00\x00\x00\x00\x00\x00\x00" # متغیر `what` را تعریف می‌کند که حاوی بایت‌های رشته ثابت `what` است. # این رشته مستقیماً از فایل باینری برنامه اصلی (که در تابع `check()` استفاده می‌شود) استخراج شده است. secret = b'B\x0A|_\x22\x06\x1Bg7#\x5CF\x0A)\x090Q8_{Y\x13\x18\x0DP\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # متغیر `secret` را تعریف می‌کند که حاوی بایت‌های کلید رمزنگاری است. # این مقدار نیز از تحلیل فایل باینری به دست آمده و برای عملیات XOR نهایی استفاده می‌شود. what = what.rstrip(b'\0') # بایت‌های NULL (`\0`) را از انتهای رشته `what` حذف می‌کند. # این کار برای اطمینان از اینکه طول دقیق داده‌ها در محاسبات بعدی استفاده شود، انجام می‌شود. secret = secret.rstrip(b'\0') # بایت‌های NULL (`\0`) را از انتهای رشته `secret` حذف می‌کند. # این کار نیز برای حفظ دقت طول رشته در عملیات XOR ضروری است. for i in range(30, 127): # این حلقه یک **بروت‌فورس (حدس زدن)** هوشمند را آغاز می‌کند. # `range(30, 127)` شامل کدهای ASCII برای کاراکترهای قابل چاپ رایج (مانند اعداد، حروف، نمادها) است. # در هر تکرار، `i` به عنوان **اولین کاراکتر احتمالی ورودی صحیح** برای تابع `check()` در نظر گرفته می‌شود. flag = [] # در هر تکرار حلقه بیرونی (`for i in ...`)، یک لیست خالی به نام `flag` ایجاد می‌شود. # این لیست `flag` در اینجا در واقع دنباله بایت‌های **ورودی صحیح برای تابع `check()`** (یعنی رشته `s` در تابع `main`) را نشان می‌دهد که ما در حال تلاش برای بازسازی آن هستیم. flag.append(i) # اولین کاراکتر حدس زده شده (`i`) به عنوان اولین بایت به لیست `flag` اضافه می‌شود. for j in range(len(what)): # این حلقه داخلی، بقیه بایت‌های دنباله ورودی صحیح (`flag` در اینجا) را بازسازی می‌کند. # این فرآیند **معکوس کردن منطق تابع `check()`** است. # از آنجایی که از تابع `check()` می‌دانیم: `(بایت_بعدی_ورودی ^ بایت_فعلی_ورودی) == what[j]` # با استفاده از خاصیت XOR (`A ^ B = C` پس `A = C ^ B`)، می‌توانیم بایت بعدی را به صورت زیر محاسبه کنیم: # `بایت_بعدی_ورودی = بایت_فعلی_ورودی ^ what[j]` flag.append(flag[j]^what[j]) # `flag[j]` (که در اینجا همان `بایت_فعلی_ورودی` است) با `what[j]` (که از رشته ثابت `what` می‌آید) XOR می‌شود. # نتیجه XOR به عنوان `بایت_بعدی_ورودی` (یعنی `flag[j+1]`) به لیست `flag` اضافه می‌شود. # این خط به صورت زنجیره‌ای بایت‌های بعدی ورودی صحیح را بر اساس بایت قبلی و مقادیر `what` بازسازی می‌کند. newFlag = "" # یک رشته خالی به نام `newFlag` ایجاد می‌شود. # این رشته برای ذخیره فلگ نهایی (که پس از رمزگشایی کاندیدای ورودی صحیح با `secret` به دست می‌آید) استفاده می‌شود. for j in range(len(what)): # این حلقه، کاندیدای ورودی صحیح بازسازی شده (که در لیست `flag` است) را با کلید `secret` XOR می‌کند. # این عملیات **شبیه‌سازی فرآیند نهایی تولید فلگ توسط برنامه اصلی** است که در صورت موفقیت `check()` انجام می‌شود. char = chr(flag[j]^secret[j%len(secret)]) # `flag[j]`: بایت `j`ام از کاندیدای ورودی صحیح بازسازی شده. # `secret[j%len(secret)]`: بایت متناظر از کلید `secret` را انتخاب می‌کند (با استفاده از عملگر `%` برای تکرار چرخشی کلید). # `^`: عملیات XOR را بین دو بایت انجام می‌دهد. # `chr()`: نتیجه عددی XOR را به کاراکتر ASCII مربوطه تبدیل می‌کند. if ord(char) not in range(30, 127): # این خط **فیلتر اصلی** این قطعه کد است و آن را از نسخه قبلی (سوم) متمایز می‌کند. # `ord(char)`: کد ASCII کاراکتر `char` را برمی‌گرداند. # `range(30, 127)`: محدوده کدهای ASCII برای کاراکترهای قابل چاپ رایج است (شامل اعداد، حروف، نمادها و برخی کاراکترهای کنترلی محدود). # `if ... not in ...`: اگر کاراکتر تولید شده **قابل چاپ نباشد** (مثلاً یک کاراکتر کنترلی، غیرقابل خواندن یا غیرمعمول باشد)... continue # ...این خط باعث می‌شود که حلقه داخلی (برای `j` فعلی) به تکرار بعدی برود و این کاراکتر به `newFlag` اضافه نشود. # این فیلتر به شدت نتایج چاپ شده را محدود می‌کند و فقط آن دسته از خروجی‌هایی را که شبیه یک فلگ معتبر (شامل کاراکترهای قابل چاپ) هستند، نمایش می‌دهد. else: # اگر کاراکتر قابل چاپ باشد (یعنی کد ASCII آن در محدوده مجاز باشد)... newFlag += char # ...آن کاراکتر به رشته `newFlag` اضافه می‌شود. print(newFlag) # هر `newFlag` تولید شده (که بر اساس هر حدس اولیه `i` و فرآیند کامل بازسازی و XOR نهایی، و پس از فیلتر کاراکترهای غیرقابل چاپ تشکیل شده است) در خروجی چاپ می‌شود. ``` دید کلی درباره این قطعه کد: این قطعه کد پایتون، که مشابه کد قبلی است اما با یک تفاوت مهم، برای یافتن فلگ واقعی با بروت‌فورس هوشمند و فیلتر کردن نتایج غیرقابل چاپ طراحی شده است. هدف آن معکوس کردن منطق تابع check() از برنامه اصلی و سپس رمزگشایی خروجی با secret است، اما فقط نتایجی را نشان می‌دهد که شبیه یک فلگ معتبر باشند. این کد یک استراتژی پیشرفته‌تر برای کشف فلگ در مقایسه با روش صرفاً رمزگشایی فلگ جعلی است. در حالی که کد قبلی فلگ جعلی را از بایت‌های هاردکد شده رمزگشایی می‌کرد، این کد تلاش می‌کند تا فلگ واقعی (که با ورودی صحیح تولید می‌شود) را بیابد. هدف اصلی: 1- یافتن ورودی صحیح: هدف اصلی این کد، پیدا کردن رشته ورودی (s در تابع main) است که باعث می‌شود تابع check() در برنامه اصلی True برگرداند. 2- تولید فلگ‌های کاندیدا: سپس، برای هر ورودی صحیح کاندیدا، خروجی نهایی برنامه را (همانطور که اگر ورودی صحیح داده می‌شد چاپ می‌شد) شبیه‌سازی می‌کند. 3- فیلتر کردن خروجی‌ها: مهم‌ترین تفاوت با کد قبلی، وجود فیلتر if ord(char) not in range(30, 127) است. این فیلتر به شدت نتایج چاپ شده را محدود می‌کند. با توجه به اینکه فلگ‌های CTF معمولاً از کاراکترهای قابل چاپ تشکیل شده‌اند، این فیلتر کمک می‌کند تا فقط آن دسته از نتایج که منطقاً می‌توانند فلگ باشند (یعنی شامل تنها کاراکترهای قابل چاپ هستند) نمایش داده شوند و نویز و خروجی‌های بی‌معنی حذف گردند. چرا این روش؟ این روش یک حدس و بازسازی کنترل‌شده است. تحلیلگر به جای اینکه میلیون‌ها ورودی تصادفی را امتحان کند، از دانش خود درباره الگوریتم check() و ماهیت کاراکترهای فلگ استفاده می‌کند تا تنها کاندیداهای معقول را تولید و آزمایش کند. احتمالاً، در بین خروجی‌های این کد، فلگ نهایی و صحیح چالش یافت خواهد شد، زیرا این همان مسیری است که برنامه برای تولید فلگ صحیح طی می‌کند. --- better_than_asm_huh Bamboofox{b3tt3r_th4n_4_d3c0mp1l3r_huh} 7h15_v3ry_l0ng_4nd_1_h0p3_th3r3_4r3_n0_7yp0