مقدمه
تا حالا شده یه نرمافزار بسته داشته باشید و بخواید بفهمید چطور با سرورش حرف میزنه؟ شاید یه پیامرسان ایرانی باشه، یه بازی آنلاین، یا حتی یه دستگاه اینترنت اشیا. شرکت سازنده مستندات پروتکل رو منتشر نکرده. تنها راه فهمیدنش، مهندسی معکوس پروتکل ارتباطیست.
توی این مقاله، قدمبهقدم یاد میگیریم چطور یه پروتکل ناشناخته رو کالبدشکافی کنیم، ساختارش رو بفهمیم، و حتی یه کلاینت شخصی براش بنویسیم. برای مثال واقعی، پروتکل یه پیامرسان فرضی به اسم «سروش» (نه محصول خودم، یه اسم فرضی) رو بررسی میکنیم.
مهندسی معکوس پروتکل یعنی چی؟
به زبان ساده، یعنی شنود و تحلیل ترافیک شبکه بین کلاینت (مثلاً اپلیکیشن موبایل) و سرور، تا بفهمیم:
هر پکت چه ساختاری داره؟
دستورات مختلف چطور کدگذاری شدن؟
اطلاعات چطور رمزنگاری شدن؟
چطور میشه این مکالمه رو شبیهسازی کرد؟
ابزارهایی که نیاز داریم:
وایرشارک (Wireshark): برای ضبط ترافیک شبکه
پایتون + اسکاپی (Scapy): برای ساخت پکتهای سفارشی
پروکسی MITM (مثل mitmproxy): برای دیدن ترافیک رمزنگاریشده (اگه HTTPS باشه)
یه دستگاه اندروید روتشده یا شبیهساز (اختیاری)
مغز کنجکاو و کمی صبر
قدم اول: شنود ترافیک خام
اولین قدم، ضبط ترافیک بین کلاینت و سروره.
کلاینت رو روی دستگاهی اجرا میکنیم که بتونیم ترافیکش رو شنود کنیم. سادهترین راه، نصب وایرشارک روی همون کامپیوتر و اجرای کلاینت (اگه نسخه ویندوز داره) یا استفاده از هاتاسپات وایرشارک روی لپتاپ و وصل کردن موبایل بهش.
بعد از چند دقیقه کار با نرمافزار (ورود، ارسال پیام، دریافت اطلاعات)، ضبط رو متوقف میکنیم. حالا یه فایل پر از پکت داریم.
اولین چیزی که توی وایرشارک میبینیم، پروتکلهای پایهست: TCP یا UDP. معمولاً پیامرسانها از TCP روی پورت ۴۴۳ (HTTPS) یا یه پورت اختصاصی استفاده میکنن. اگه HTTPS باشه، کارمون سختتره (بعداً میگم چطور). فعلاً فرض میکنیم پروتکل روی TCP خام یا WebSocket هست.
قدم دوم: پیدا کردن الگوهای تکراری
حالا توی وایرشارک، روی یه پکت مربوط به نرمافزار کلیک راست میکنیم و گزینه Follow TCP Stream رو میزنیم. اینجا مکالمه کامل بین کلاینت و سرور رو میبینیم.
چندتا درخواست و پاسخ رو بررسی میکنیم. دنبال این الگوها میگردیم:
بایتهای ثابت در ابتدای هر پکت: خیلی از پروتکلها با یه Header ثابت شروع میشن. مثلاً ۴ بایت اول طول پکت رو نشون میده، یا یه Magic Number هست مثل 0xFEEDFACE.
فیلد طول: معمولاً ۲ یا ۴ بایت اول (یا بعد از Magic) طول کل پکت یا طول بخش Data رو مشخص میکنن.
فیلد نوع دستور: یه بایت یا عدد کوچیک که نوع عملیات رو میگه (مثلاً 0x01 برای ورود، 0x02 برای ارسال پیام).
فیلدهای متغیر: بخشهایی که توی هر پکت فرق میکنن (مثل نام کاربری، محتوای پیام).
مثال واقعی (فرضی)
فرض کن بعد از تحلیل چند پکت ورود، این بایتها رو میبینیم:
پکت اول (درخواست ورود):
AA BB 00 1C 01 00 00 00 05 61 64 6D 69 6E 00 00 00 08 70 61 73 73 77 6F 72 64
پکت دوم (درخواست ارسال پیام):
AA BB 00 18 02 00 00 00 09 48 65 6C 6C 6F 20 57 6F 72 6C 64
چی میبینیم؟
دو بایت اول همیشه AA BB هستن. این احتمالاً Header Magic هست.
دو بایت بعدی (00 1C و 00 18) مقادیر ۲۸ و ۲۴ هستن. اگه طول کل پکت رو حساب کنیم، میبینیم دقیقاً برابر با این اعداد هست. پس این فیلد Length Packet هست.
بایت پنجم (01 و 02) توی پکتها فرق داره. احتمالاً Type Command هست (۱ برای ورود، ۲ برای پیام).
بعد از اون، ۴ بایت (00 00 00 05) عدد ۵ رو نشون میده. بعدش ۵ بایت داریم (admin). پس این فیلد Length + Data برای نام کاربریست.
بعدش ۴ بایت (00 00 00 08) عدد ۸ و ۸ بایت (password). اینم Length + Data برای رمز عبور.
پکت دوم هم ساختار مشابهی داره: Length + Data برای متن پیام.
قدم سوم: مستندسازی ساختار پروتکل
حالا میتونیم ساختار پروتکل رو اینطور تعریف کنیم:
آفستاندازهنام فیلدتوضیح۰۲Magicهمیشه 0xAABB۲۲Packet Lengthطول کل پکت (با احتساب هدر)۴۱Command Type۱: ورود، ۲: پیام، ۳: خروج۵۴Data1 Lengthطول اولین رشته۹متغیرData1اولین رشته۹+n۴Data2 Lengthطول دومین رشته۱۳+nمتغیرData2دومین رشته
حالا میتونیم برای هر Command Type، معنی Data1 و Data2 رو مشخص کنیم:
Type 1 (ورود): Data1 = نام کاربری، Data2 = رمز عبور
Type 2 (ارسال پیام): Data1 = نام گیرنده، Data2 = متن پیام
قدم چهارم: ساخت کلاینت شخصی با پایتون
حالا که ساختار رو فهمیدیم، میتونیم یه کلاینت ساده با پایتون بنویسیم که بدون نیاز به اپ رسمی، با سرور حرف بزنه:
import socket
import struct
def send_login(sock, username, password):
"""ارسال درخواست ورود به سرور"""
username_bytes = username.encode('utf-8')
password_bytes = password.encode('utf-8')
data = struct.pack('>I', len(username_bytes)) + username_bytes
data += struct.pack('>I', len(password_bytes)) + password_bytes
packet = b'\xAA\xBB'
packet += struct.pack('>H', 5 + len(data))
packet += b'\x01'
packet += data
sock.send(packet)
response = sock.recv(1024)
return response
def send_message(sock, recipient, message):
"""ارسال پیام به کاربر دیگر"""
recip_bytes = recipient.encode('utf-8')
msg_bytes = message.encode('utf-8')
data = struct.pack('>I', len(recip_bytes)) + recip_bytes
data += struct.pack('>I', len(msg_bytes)) + msg_bytes
packet = b'\xAA\xBB'
packet += struct.pack('>H', 5 + len(data))
packet += b'\x02'
packet += data
sock.send(packet)
response = sock.recv(1024)
return response
sock = socket.socket(_INET, socket.SOCK_STREAM)
sock.connect((', 12345))
response = send_login(sock, 'admin', 'password123')
print(f"Login response: {response.hex()}")
response = send_message(sock, 'user2', 'Hello World!')
print(f"Message response: {response.hex()}")
sock.close()
قدم پنجم: چالشها و راهحلها
چالش ۱: ترافیک HTTPS
اگه نرمافزار از HTTPS استفاده کنه، وایرشارک فقط داده رمزنگاریشده نشون میده. راهحل:
استفاده از mitmproxy با نصب گواهی جعلی روی دستگاه
استفاده از Frida برای Hook کردن توابع رمزنگاری در اپلیکیشن
پیدا کردن نسخه قدیمیتر اپ که شاید HTTP ساده استفاده میکرده
چالش ۲: رمزنگاری سفارشی
بعضی پروتکلها علاوه بر TLS، یه لایه رمزنگاری اختصاصی هم دارن. راهحل:
مهندسی معکوس فایل APK یا EXE با ابزارهایی مثل Ghidra یا IDA
پیدا کردن تابع رمزنگاری و بازنویسی اون در پایتون
استفاده از Frida برای صدا زدن مستقیم تابع رمزنگاری از داخل اپ
چالش ۳: فشردهسازی داده
بعضی پروتکلها از zlib یا gzip برای فشردهسازی استفاده میکنن. اگه بعد از رمزگشایی، داده باز هم غیرقابل خوندن بود، احتمالاً فشردهست. راهحل:
امتحان کردن zlib.decompress روی داده
جستجوی رشتههایی مثل «zlib» یا «gzip» در فایل اجرایی
نتیجهگیری
مهندسی معکوس پروتکلهای اختصاصی نه جادو هست، نه غیرممکن. فقط نیاز به صبر، دقت، و یه ذره خلاقیت داره. با تکنیکهایی که یاد گرفتیم، میتونیم پرده از راز مکالمات هر نرمافزاری برداریم.
البته یادتون باشه: این دانش رو فقط برای اهداف قانونی مثل تست نفوذ مجاز، پژوهش امنیتی، یا ساخت کلاینتهای متنباز جایگزین استفاده کنید.
سوالی دارید؟ پایین همین پست بپرسید.