ویرگول
ورودثبت نام
عرفان محمدی
عرفان محمدیتوسعه‌دهنده نرم‌افزار برنامه نویس فول استک و پژوهشگر امنیت سایبری علاقه مند به فضا
عرفان محمدی
عرفان محمدی
خواندن ۵ دقیقه·۱ ماه پیش

کد ماشین و دنیای صفر و یک؛ زبان واقعی کامپیوترها

## مقدمه: زیر پوست نرم‌افزارها چه خبره؟

ما هر روز با پایتون کد می‌زنیم، با جاوااسکریپت وب می‌سازیم، با گو سرور بالا می‌آریم. اما تا حالا فکر کردی زیر این همه لایه انتزاع، واقعاً چی داره اتفاق می‌افته؟

کامپیوتر فقط صفر و یک می‌فهمه. نه پایتون، نه گو، نه هیچ زبان دیگری. هر خط کدی که می‌نویسی، نهایتاً به یه رشته طولانی از ۰۱۰۱۰۱ تبدیل می‌شه.

توی این مقاله می‌خوایم سفر کنیم به عمیق‌ترین لایه کامپیوتر. جایی که خبری از متغیر و تابع و کلاس نیست. فقط بیت‌ها و ثبات‌ها و پرش‌ها.

## زبان ماشین چیه دقیقاً؟

زبان ماشین (Machine Code) مجموعه‌ای از دستورات عددی‌ست که مستقیماً توسط سی‌پی‌یو اجرا می‌شه. هر خانواده پردازنده (مثل x86، ARM، RISC-V) زبان ماشین مخصوص به خودش رو داره.

یه مثال ساده از زبان ماشین x86 (به صورت هگزادسیمال):

B8 05 00 00 00 ; یعنی عدد ۵ رو بریز توی ثبات EAX

83 C0 03 ; عدد ۳ رو به EAX اضافه کن

C3 ; برگرد به تابع صدا زننده

همین! این سه خط، معادل یه تابع ساده‌ست که ۵ رو با ۳ جمع می‌کنه و برمی‌گردونه.

## چطور کد پایتون تبدیل به صفر و یک می‌شه؟

بیایم مسیر تبدیل یه کد ساده پایتون به زبان ماشین رو دنبال کنیم:

def add(a, b):

return a + b

### مرحله ۱: کد پایتون به بایت‌کد پایتون

پایتون اول کد رو به یه فرم میانی به اسم بایت‌کد (Bytecode) تبدیل می‌کنه. می‌تونیم ببینیمش:

import dis

dis.dis(add)

# خروجی:

# 2 0 LOAD_FAST 0 (a)

# 2 LOAD_FAST 1 (b)

# 4 BINARY_ADD

# 6 RETURN_VALUE

این بایت‌کد توسط ماشین مجازی پایتون اجرا می‌شه، نه مستقیم توسط سی‌پی‌یو.

### مرحله ۲: ماشین مجازی پایتون به زبان C

خود مفسر پایتون با C نوشته شده. وقتی BINARY_ADD اجرا می‌شه، در واقع یه تابع C صدا زده می‌شه که دو عدد رو جمع می‌کنه.

### مرحله ۳: کد C به زبان اسمبلی

کامپایلر C (مثل GCC) کد C رو به اسمبلی تبدیل می‌کنه:

mov eax, [a] ; مقدار a رو بریز توی EAX

add eax, [b] ; مقدار b رو به EAX اضافه کن

ret ; برگرد

### مرحله ۴: اسمبلی به کد ماشین

اسمبلر هر دستور رو به معادل عددیش تبدیل می‌کنه:

| دستور اسمبلی | کد ماشین (هگز) | توضیح |

| mov eax, [a] | 8B 45 ?? | اپ‌کد 8B یعنی MOV |

| add eax, [b] | 03 45 ?? | اپ‌کد 03 یعنی ADD |

| ret | C3 | اپ‌کد C3 یعنی RET |

نهایتاً سی‌پی‌یو این بایت‌ها رو می‌خونه و اجرا می‌کنه:

8B 45 FC 03 45 F8 C3

(اعداد FC و F8 محل متغیرها توی پشته هستن)

## نگاهی به داخل یه فایل اجرایی

یه فایل exe ویندوز یا ELF لینوکس فقط یه سری بایت پشت سر هم نیست. ساختار پیچیده‌ای داره:

| بخش (Section) | محتوا |

| .text | کد ماشین قابل اجرا |

| .data | متغیرهای مقداردهی اولیه شده |

| .bss | متغیرهای مقداردهی نشده |

| .rodata | داده‌های فقط‌خواندنی (مثل رشته‌ها) |

| .plt | جدول پیوند رویه (برای کتابخونه‌های پویا) |

می‌تونیم با ابزار objdump محتوای یه فایل اجرایی رو ببینیم:

objdump -d myprogram

# خروجی (بخشی از کد):

08048400 <main>:

8048400: 55 push ebp

8048401: 89 e5 mov ebp,esp

8048403: 83 ec 10 sub esp,0x10

8048406: c7 45 fc 05 00 00 00 mov DWORD PTR [ebp-0x4],0x5

804840d: 8b 45 fc mov eax,DWORD PTR [ebp-0x4]

8048410: c9 leave

8048411: c3 ret

این دقیقاً همون چیزیه که سی‌پی‌یو می‌بینه و اجرا می‌کنه.

## چطور کد ماشین رو مستقیم بنویسیم و اجرا کنیم؟

با پایتون می‌شه کد ماشین رو مستقیم توی حافظه نوشت و اجرا کرد! (البته فقط روی سیستم‌های ساده یا شبیه‌ساز)

import ctypes

# کد ماشین برای: mov eax, 42; ret

# (مخصوص x86-64)

machine_code = bytes([

0xB8, 0x2A, 0x00, 0x00, 0x00, # mov eax, 42

0xC3 # ret

])

# تخصیص حافظه قابل اجرا

buf = ctypes.create_string_buffer(machine_code)

exec_func = ctypes.CFUNCTYPE(ctypes.c_int)(ctypes.addressof(buf))

# اجرای کد ماشین

result = exec_func()

print(f"نتیجه: {result}") # خروجی: 42

این کد واقعاً یه تابع به زبان ماشین می‌سازه و اجراش می‌کنه!

## مهندسی معکوس: از صفر و یک به کد قابل فهم

همونطور که می‌شه از بالا به پایین رفت (کد → ماشین)، می‌شه از پایین به بالا هم رفت (ماشین → کد). به این می‌گن مهندسی معکوس.

ابزارهایی مثل Ghidra (سازمان امنیت ملی آمریکا!) و IDA Pro کد ماشین رو به اسمبلی و حتی به یه نسخه تقریبی از C تبدیل می‌کنن.

مثال واقعی: یه فایل اجرایی داری و می‌خوای بفهمی چطور کار می‌کنه:

۱. فایل رو توی Ghidra باز می‌کنی

۲. کد ماشین به اسمبلی تبدیل می‌شه:

MOV EAX, dword ptr [RBP + -0x4]

CMP EAX, 0x7a69

JNZ LAB_00101234

۳. Ghidra حدس می‌زنه کد C معادل چی بوده:

if (local_variable == 31337) {

// do something

}

اینجوری می‌شه الگوریتم‌های رمزنگاری، چک‌های لایسنس، و حتی بک‌دورها رو از توی فایل اجرایی بیرون کشید.

## چرا دونستن زبان ماشین برای هکرها و محققان امنیت حیاتی‌ست؟

| کاربرد | توضیح |

| اکسپلویت نویسی | برای نوشتن شل‌کد باید زبان ماشین رو بلد باشی |

| مهندسی معکوس بدافزار | بدافزارها معمولاً بدون سورس هستن |

| تحلیل آسیب‌پذیری‌ها | خیلی از باگ‌ها فقط توی سطح اسمبلی دیده می‌شن |

| بهینه‌سازی کد | بفهمی کامپایلر چطور کدت رو بهینه می‌کنه |

| درک عمیق سیستم‌عامل | بوت‌لودر، کرنل، وقفه‌ها - همه به زبان ماشین هستن |

## جمع‌بندی: زیر همه انتزاع‌ها، صفر و یک حکومت می‌کنه

ما برنامه‌نویس‌ها عادت کردیم توی دنیای انتزاعی زندگی کنیم. تابع، کلاس، آبجکت، جنریک، لامبدا... همه اینا دروغ‌های مفیدی هستن که بهمون اجازه می‌دن بدون دیوونه شدن، نرم‌افزار بسازیم.

اما حقیقت اینه: زیر همه اینا، یه سی‌پی‌یو هست که فقط یه کار بلده:

۱. یه بایت از حافظه بخونه

۲. بفهمه یعنی چی (مثلاً B8 یعنی MOV)

۳. اجراش کنه

۴. بره سراغ بایت بعدی

همین. تمام نرم‌افزارهای دنیا، از مرورگر کروم گرفته تا هوش مصنوعی چت‌جی‌پی‌تی، نهایتاً یه رشته طولانی از همین دستورات ساده هستن.

و این، به نظر من، جادوی واقعی کامپیوتره.

سوالی در مورد صفر و یک داری؟ پایین بپرس.

مهندسی نرم افزاربرنامه نویسیکامپیوتر
۱۱
۰
عرفان محمدی
عرفان محمدی
توسعه‌دهنده نرم‌افزار برنامه نویس فول استک و پژوهشگر امنیت سایبری علاقه مند به فضا
شاید از این پست‌ها خوشتان بیاید