در قسمت قبل با فازهای کامپایل یک فایل سی آشنا شدیم.در این قسمت و قسمت های آینده نیز به مرور وارد مباحث سطح پایین میشویم و به بررسی بیشتر فایل های باینری میپردازیم.
در قسمت قبل پس از بررسی مراحل کامپایل ، هویت فایل اجرایی را بررسی کردیم و به اطلاعاتی رسیدم.قرارمان بر این شد تا آخرین خروجی ابزار file را بررسی کنیم.به صورت پیشفرض، خروجی کامپایلر gcc به صورت a.out است.ولی میتوانیم با فلگ o- نامی معین برای فایل اجراییمان انتخاب کنیم.
با توجه به خروجی ابزار file در شکل بالا میدانیم که با یک فایل اجرایی 64 بیتی ELF سروکار داریم.از دیگر اطلاعات مهم در خروجی بالا میتوان به داینامیک بودن این فایل اشاره کرد.به این معنا که از کتابخانه هایی استفاده میکند که به طور مستقیم در فایل اجرایی ادغام نمیشوند.در واقع کتابخانه های نصب شده بر روی سیستمعامل به صورت مجزا با آن به اشتراک گذاشته میشود.سرانجام عبارت interpreter /lib64/ld-linux-x86-64.so.2 به شما میگوید که فایل آبجکت توسط کدام لینکر تفسیر و تبدیل به فایل اجرایی شده.
هنگام کامپایل یک سورس کد به یک زبان سطح بالا(مانند سیپلاسپلاس) ،فایل کامپایل شده میتوانند حاوی اطلاعاتی باشد که برای اجرای برنامه ضروری نیست اما برای دیباگینگ و یافتن مشکلات موجود در برنامه مفید هستند.از میان این اطلاعات میتوان به نام توابع،متغییر ها و سایر سمبلها نظیر آدرس های حافظه اشاره کرد.
برای آنکه یک ایده کلی از اطلاعات موجود در این سمبل ها داشته باشید ، من از ابزار readelf در لینوکس استفاده میکنم.readelf یکی از ابزار های قدرتمند برای تحلیل فایلهای اجرایی ELF در سیستم عامل های خانواده یونیکس است.خوشبختانه این ابزار دارای یک manual page است که میتوانید برای کسب اطلاعات بیشتر آن را با این دستور مشاهده کنید: man readelf .
با استفاده از دستور readelf –syms a.out برنامه ساخته شده در قسمت گذشته را مورد بررسی قرار میدهیم.برای بررسی بهتر، خروجی را در اختیارتان قرار میدهم:
Symbol table '.dynsym' contains 7 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterT[...] 2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2) 3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [...]@GLIBC_2.2.5 (2) 4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMC[...] 6: 0000000000000000 0 FUNC WEAK DEFAULT UND [...]@GLIBC_2.2.5 (2) Symbol table '.symtab' contains 66 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000000000002a8 0 SECTION LOCAL DEFAULT 1 2: 00000000000002c4 0 SECTION LOCAL DEFAULT 2 3: 00000000000002e8 0 SECTION LOCAL DEFAULT 3 4: 0000000000000308 0 SECTION LOCAL DEFAULT 4 5: 0000000000000328 0 SECTION LOCAL DEFAULT 5 6: 00000000000003d0 0 SECTION LOCAL DEFAULT 6 7: 0000000000000452 0 SECTION LOCAL DEFAULT 7 8: 0000000000000460 0 SECTION LOCAL DEFAULT 8 9: 0000000000000480 0 SECTION LOCAL DEFAULT 9 10: 0000000000000540 0 SECTION LOCAL DEFAULT 10 11: 0000000000001000 0 SECTION LOCAL DEFAULT 11 12: 0000000000001020 0 SECTION LOCAL DEFAULT 12 13: 0000000000001040 0 SECTION LOCAL DEFAULT 13 14: 00000000000011c8 0 SECTION LOCAL DEFAULT 14 15: 0000000000002000 0 SECTION LOCAL DEFAULT 15 16: 0000000000002014 0 SECTION LOCAL DEFAULT 16 17: 0000000000002048 0 SECTION LOCAL DEFAULT 17 18: 0000000000003de8 0 SECTION LOCAL DEFAULT 18 19: 0000000000003df0 0 SECTION LOCAL DEFAULT 19 20: 0000000000003df8 0 SECTION LOCAL DEFAULT 20 21: 0000000000003fd8 0 SECTION LOCAL DEFAULT 21 22: 0000000000004000 0 SECTION LOCAL DEFAULT 22 23: 0000000000004020 0 SECTION LOCAL DEFAULT 23 24: 0000000000004030 0 SECTION LOCAL DEFAULT 24 25: 0000000000000000 0 SECTION LOCAL DEFAULT 25 26: 0000000000000000 0 FILE LOCAL DEFAULT ABS abi-note.c 27: 00000000000002e8 32 OBJECT LOCAL DEFAULT 3 __abi_tag 28: 0000000000000000 0 FILE LOCAL DEFAULT ABS init.c 29: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c 30: 0000000000001070 0 FUNC LOCAL DEFAULT 13 deregister_tm_clones 31: 00000000000010a0 0 FUNC LOCAL DEFAULT 13 register_tm_clones 32: 00000000000010e0 0 FUNC LOCAL DEFAULT 13 __do_global_dtors_aux 33: 0000000000004030 1 OBJECT LOCAL DEFAULT 24 completed.0 34: 0000000000003df0 0 OBJECT LOCAL DEFAULT 19 __do_global_dtor[...] 35: 0000000000001130 0 FUNC LOCAL DEFAULT 13 frame_dummy 36: 0000000000003de8 0 OBJECT LOCAL DEFAULT 18 __frame_dummy_in[...] 37: 0000000000000000 0 FILE LOCAL DEFAULT ABS file.c 38: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c 39: 000000000000211c 0 OBJECT LOCAL DEFAULT 17 __FRAME_END__ 40: 0000000000000000 0 FILE LOCAL DEFAULT ABS 41: 0000000000003df0 0 NOTYPE LOCAL DEFAULT 18 __init_array_end 42: 0000000000003df8 0 OBJECT LOCAL DEFAULT 20 _DYNAMIC 43: 0000000000003de8 0 NOTYPE LOCAL DEFAULT 18 __init_array_start 44: 0000000000002014 0 NOTYPE LOCAL DEFAULT 16 __GNU_EH_FRAME_HDR 45: 0000000000004000 0 OBJECT LOCAL DEFAULT 22 _GLOBAL_OFFSET_TABLE_ 46: 0000000000001000 0 FUNC LOCAL DEFAULT 11 _init 47: 00000000000011c0 5 FUNC GLOBAL DEFAULT 13 __libc_csu_fini 48: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterT[...] 49: 0000000000004020 0 NOTYPE WEAK DEFAULT 23 data_start 50: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@@GLIBC_2.2.5 51: 0000000000004030 0 NOTYPE GLOBAL DEFAULT 23 _edata 52: 00000000000011c8 0 FUNC GLOBAL HIDDEN 14 _fini 53: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_mai[...] 54: 0000000000004020 0 NOTYPE GLOBAL DEFAULT 23 __data_start 55: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 56: 0000000000004028 0 OBJECT GLOBAL HIDDEN 23 __dso_handle 57: 0000000000002000 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used 58: 0000000000001150 101 FUNC GLOBAL DEFAULT 13 __libc_csu_init 59: 0000000000004038 0 NOTYPE GLOBAL DEFAULT 24 _end 60: 0000000000001040 47 FUNC GLOBAL DEFAULT 13 _start 61: 0000000000004030 0 NOTYPE GLOBAL DEFAULT 24 __bss_start 62: 0000000000001139 23 FUNC GLOBAL DEFAULT 13 main 63: 0000000000004030 0 OBJECT GLOBAL HIDDEN 23 __TMC_END__ 64: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMC[...] 65: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@@[...]
همانطور که مشاهده میکنید نمادها و سمبل هایی در خروجی این دستور نشان داده شد.در پارت های آینده این ابزار را بیشتر مورد بررسی قرار خواهم داد.از نکات جالب در این خروجی میتوان به خط 75 اشاره کرد.در این خط مقدار main در آدرس 0x001139 بارگیری(load) و با سایز 23 بایت ذخیره شده.همچنین نوع این سمبل از نوع FUNC به معنای تابع است.
اطلاعات سمبلیک را میتوان به عنوان بخشی از یک فایل باینری (مثال بالا) و یا به صورت یک فایل سمبلیک به صورت مجزا منتشر کرد.لینکر فقط به سمبلهای اساسی نیاز دارد،اما اطلاعات گستردهتری را میتواند برای دیباگینگ منتشر کند.
در فایلهای باینری با فرمت اجرایی ELF ، سمبلهای دیباگینگ معمولا در قابل فرمت DWARF تولید میشوند. درحالی که باینری های PE معمولا از قالب اختصاصی Microsoft Portable Debugging (به اختصار PDB) استفاده میکنند.اطلاعات DWARF معمولا در خود باینری جاسازی میشوند،درحالی که در (PDB) به شکل یک فایل جداگانه در می آیند.
احتمالا متوجه شده باشید که اطلاعات سمبلیک میتوانند برای آنالیز فایلهای باینری مفید باشند.به عنوان مثال شما با داشتن چنین اطلاعاتی میتوانید فرایند دیساسمبل و تحلیل آن را بسیار آسان کنید زیرا میتوانید از هر سمبلی به عنوان نقطه شروع دیساسمبل کردن استفاده کنید.دانستن اینکه کدام قسمت باینری به کدام تابع تعلق دارد میتواند کار را برای مهندسی معکوس راحتتر کند.
اگر یادتان باشد در مثالی که زده شد،فایل تولید شده از نوع استریپ نشده بود.ظاهرا gcc به صورت پیشفرض کد را استریپ نمیکند.در صورتی که کنکجاو هستید که این سمبل ها چگونه از بین میرود،باید بگویم این کار ساده است.میتوانید از ابزار strip به این شکل استفاده کنید :
strip -strip-all execfile
اکنون که با نحوه کامپایل آشنا شدیم،بیاید نگاهی به محتویات آبجکت فایل تولید شده در فاز اسمبلی بیندازیم.پس از آن من فایل قابل اجرای باینری را دیساسمبل میکنم تا به شما تفاوت محتوای این دو را نشان دهم.به این ترتیب درک واضحتری از آنچه در یک آبجکت فایل است و آنچه در مرحله لینکر ساخته میشود خواهیم گرفت.
در حال حاضر من از ابزار objdump برای نشان دادن نحوه دیساسمبل کردن آن استفاده خواهم کرد.این ابزار یک دیساسمبلر ساده و پرکاربرد است که در اکثر توزیع های لینوکسی به صورت دیفالت وجود دارد. همچنین برای ایده گرفتن سریع از کد و داده های موجود در باینری بسیار عالی است.
اگر به تصویر دقت کنید خواهید دید که من دوبار objdump را فراخوانی کردم.ابتدا برای مشاهده سکشن rodata. که مخفف read-only-data است و بخشی از محتویات باینری در آن ذخیره میشود.از جمله استرینگ Hello,World که در کد وارد کرده بودیم.توجه داشته باشید که محتویات rodata. از یک کدگذاری اسکی(ASCII) تشکیل شده است که میتوانید آن را در سمت چپ خروجی و در سمت راست نیز بایت های قابل خوانش برای انسان را مشاهده کنید.دومین فراخوانی برای دیساسمبل کردن تمام آبجکت فایل با سینتکس اینتل استفاده شده است.
file.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <main>: 0: 55 push rbp 1: 48 89 e5 mov rbp,rsp 4: 48 8d 3d 00 00 00 00 lea rdi,[rip+0x0] # b <main+0xb> b: e8 00 00 00 00 call 10 <main+0x10> 10: b8 00 00 00 00 mov eax,0x0 15: 5d pop rbp 16: c3 ret
همانطور که مشاهده میکنید،این تنها شامل کد تابع main برنامه است(خط6) که آن را قبلا در داخل سورس کد تعریف کردیم.در اکثر مواقع ، این خروجی با کد اسمبلی که قبلا آن را در فاز کامپایل دیدیم مطابقت دارد.چیزی که در این کد باید به آن توجه کنیم،فراخوانی دهمین آفست تابع main است و مشخص نیست چه چیزی فراخوانی میشود اما چرا؟!چرا این فراخوانی به جای یک تابع به مکان نامشخصی اشاره میکند؟
درواقع در آبجکت فایل ها منابع و سورس کد تعیین نشده و وظیفه تعیین کردن هربخش برعهده لینکر است.برای مشاهده رفرنس های این فایل از همان ابزار readelf و با فلگ relocs استفاده میکنیم.این فلگ محتویات بخش relocation را درصورت وجود نشان میدهد.
Relocation section '.rela.text' at offset 0x1f8 contains 2 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000007 000500000002 R_X86_64_PC32 0000000000000000 .rodata - 4
00000000000c 000b00000004 R_X86_64_PLT32 0000000000000000 puts - 4
Relocation section '.rela.eh_frame' at offset 0x228 contains 1 entry:
Offset Info Type Sym. Value Sym. Name + Addend
000000000020 000200000002 R_X86_64_PC32 0000000000000000 .text + 0
در خط سوم به لینکر گفته میشود که باید آدرس های فراخوانی را تعیین کند تا برنامه بتواند به هر آدرسی از سکشن rodata. دسترسی پیدا کند.همچنین در خط چهارم به لینکر گفته میشود که چگونه میتواند فراخوانی تابع Puts را تعیین کند.
به جهت طولانی نشدن مطلب آن را همینجا به اتمام میرسانم در قسمت سوم به بررسی فایل های اجرایی خواهم پرداخت.