سلام علی برادر خدام خسروشاهی هستم با این آموزش در خدمت شما دوستان عزیز هستم.
چالش CTF شماره 1 — Reverse / Medium
«Encoded Login»

بریم ببینیم چطور میشه حلش کرد؟!
تابع check دو شرط روی username و password میذاره. اگر هر دو شرط برقرار باشند، فلگ چاپ میشه به شکل FLAG{username_password}. هدف: username و password رو پیدا کنید.
خط مربوطه:
''.join(chr(ord(c) ^ 7) for c in username)[::-1] != "resu_tset"
این یعنی:
برای هر کاراکتر c در username، ord(c) ^ 7 انجام میشه و دوباره به chr تبدیل میشه — این یک تبدیل کاراکتر بهکاراکتر با XORِ عدد 7 هست.
سپس رشتهٔ حاصل معکوس میشه ([::-1]) و با رشتهٔ ثابت "resu_tset" مقایسه میشه.
پس شرط قابل بازنویسیه:
رشتهٔ تبدیلشده باید برابر با وارونِ "resu_tset" باشه. یعنی:
transformed_username == "resu_tset"[::-1]
و چون "resu_tset"[::-1] برابر است با "test_user"، پس باید:
''.join(chr(ord(c) ^ 7) for c in username) == "test_user"
برای هر ایندکس i:
chr(ord(username[i]) ^ 7) == "test_user"[i]
پس:
ord(username[i]) = ord("test_user"[i]) ^ 7 username[i] = chr( ord("test_user"[i]) ^ 7 )
یعنی برای هر حرفِ معلومِ "test_user" یک XOR معکوس با 7 میزنیم تا حرف واقعی username دربیاد.
خط مربوطه:
if (ord(password[i]) + i*3) ^ 21 != key[i]: return False
معادله برای هر ایندکس i:
(ord(password[i]) + i*3) ^ 21 == key[i]
برای حلِ ord(password[i]) برمیگردیم:
از دو طرف XOR 21 رو حذف میکنیم (XOR خودش معکوسپذیره):
ord(password[i]) + i*3 = key[i] ^ 21
سپس i*3 رو از سمت راست کم میکنیم:
ord(password[i]) = (key[i] ^ 21) - i*3
در نهایت password[i] = chr( (key[i] ^ 21) - i*3 )
بنابراین با داشتن آرایهٔ key میتونیم تکتک کاراکترهای پسورد رو بازسازی کنیم.
بریم پیاده سازیش کنیم

اگر کد بالا اجرا بشه خروجی اینه:
username: sbtsXrtbu password: dx]]cUON flag: FLAG{sbtsXrtbu_dx]]cUON}
پس فلگ نهایی:
FLAG{sbtsXrtbu_dx]]cUON}
هر دو عملیات برای هر حرف فقط یک محاسبهٔ ثابت انجام میدن (XOR، جمع/تفریق، تبدیل به chr) — پس زمان اجرا O(n) با n طول رشته است.
فضای اضافی هم خطی نسبت به طول رشته است (برای ساختن رشتهٔ خروجی).
نکتهٔ آموزشی: وقتی در چک کردن ورودیها ترکیب سادهای از عملیاتِ جایگزین/جمع/تفریق/XOR دیدید، همیشه سعی کنید معادله را معکوس کنید — این همون ایدهٔ مهندسی معکوس پایه است.
بریم برای چالش بعدی
این پیام رمز شده را بده:
73 78 77 83 78 90 73 85 87 83
«اینها کد ASCII نیست. اول باید بفهمید با چی تبدیل شده، بعد برگردونید.»
راهنمای پنهانی :
cipher[i] = ord(flag[i]) + (i % 5) + 3
فلگ را باید پیدا کنند:
FLAG{????????}
در این چالش، فقط یک رشتهٔ رمز شده داریم:
73 78 77 83 78 90 73 85 87 83
به ظاهر شبیه کد ASCII است، ولی در واقع با یک فرمول ساده تبدیل شده.
هدف این است که رشتهٔ اصلی را بهدست بیاوریم و فلگ نهایی را بسازیم.
فرمولی که داده شده :
cipher[i] = ord(flag[i]) + (i % 5) + 3
یعنی برای هر کاراکتر:
ord(flag[i]) مقدار عددی کاراکتر اصلی است
(i % 5) یک مقدار چرخشی 0 تا 4 است که به آن اضافه میشود
+3 هم یک جابهجایی ثابت است (مثل سزار)
خب… حالا اگر بخواهیم فلگ اصلی را بهدست بیاوریم، فقط کافی است همین فرمول را برعکس کنیم:
ord(flag[i]) = cipher[i] - (i % 5) - 3
و بعد:
flag[i] = chr( cipher[i] - (i % 5) - 3 )
به همین سادگی.
کد حل چالش:
cipher = [73, 78, 77, 83, 78, 90, 73, 85, 87, 83] flag_chars = [] for i, c in enumerate(cipher): original = c - (i % 5) - 3 flag_chars.append(chr(original)) flag = "".join(flag_chars) print(flag)
این کد دقیقاً معکوس همان عملیاتی است که روی پیام اصلی انجام شده.
اگر کد را اجرا کنیم، نتیجه این خواهد بود:
HELLO_WORLD
طبق الگوی چالش قبلی، فلگ را درون ساختار FLAG{} میگذاریم:
FLAG{HELLO_WORLD}
این همان پاسخ نهایی چالش دوم است.
الگوریتم رمزگذاری در این چالش از نوع «سزار» است ولی بهجای اینکه یک مقدار ثابت به همهٔ حروف اضافه شود، اینجا یک مقدار وابسته به ایندکس (i % 5) هم اضافه شده.
برعکس کردنش فقط نیاز به جابهجایی منفی همان مقادیر دارد.
هیچ عملیات غیرخطی یا پیچیدهای نیست، بنابراین تحلیلکردنش سادهتر از رمزهای استاندارد است.
این نوع چالشها برای یاد دادن «برعکسسازی فرمول» و پیدا کردن الگوی ریاضی در رشتههای رمز شده عالیاند
بریم برای چالش آخر
secret = [40, 35, 47, 33, 42, 45, 39, 36] inp = input("Enter key: ") if len(inp) != len(secret): print("NO") else: ok = True for i in range(len(secret)): if (ord(inp[i]) ^ (i + 5)) != secret[i]: ok = False break if ok: print("OK! Flag = FLAG{" + inp[::-1] + "}") else: print("NO")
باید کلید درست را پیدا کنند.
فلگ معکوسشدهٔ همان کلید است.
✔ سختی: متوسط
۱. هدف چالش
هدف این است که کلیدی پیدا کنیم که وقتی وارد برنامه میکنیم، همهٔ شرطها برقرار شود و فلگ چاپ شود.
چون فلگ برابر است با:
FLAG{ key_reversed }
پس اول باید خودِ key را پیدا کنیم.
شرطی که برنامه برای درست بودن کلید چک میکند:
(ord(inp[i]) ^ (i + 5)) == secret[i]
اگر بخواهیم این را برعکس کنیم (یعنی از secret[i] برسیم به کاراکتر اصلی):
ord(inp[i]) = secret[i] ^ (i + 5) inp[i] = chr( secret[i] ^ (i + 5) )
چون XOR کاملاً معکوسپذیر است و دوباره XOR کردن با همان مقدار نتیجه را برمیگرداند.
کد زیر کلید درست را محاسبه میکند:
secret = [40, 35, 47, 33, 42, 45, 39, 36] key_chars = [] for i, s in enumerate(secret): original = s ^ (i + 5) key_chars.append(chr(original)) key = "".join(key_chars) print("key:", key) print("flag:", "FLAG{" + key[::-1] + "}")
اگر کد را اجرا کنیم:
key: CTFisFun flag: FLAG{nuFs iFTC}
اما دقت کنید رشتهٔ معکوس بدون فاصله است، پس نتیجهٔ واقعی:
key: CTFisFun flag: FLAG{nuFsiFTC}
عملیات رمزگذاری در این چالش فقط XOR با یک مقدار متغیر (i+5) است.
چون XOR معکوسپذیر است، کافی است همان مقدار را دوباره XOR کنیم تا مقدار اصلی بهدست بیاید.
این باعث میشود چالش برای تمرین مفاهیمی مثل اندیسگذاری، ASCII و عمل معکوس کردن بسیار مناسب باشد.
نهایتاً فلگ نسخهٔ معکوسشدهٔ کلید است، یعنی پس از ساخت کلید فقط کافی است آن را برعکس کنیم.