
باللغة الروسية ، لا توجد معلومات كافية حول كيفية التعامل مع ملفات ELF (التنسيق القابل للتنفيذ والربط - التنسيق الرئيسي للملفات القابلة للتنفيذ لنظام Linux والعديد من أنظمة Unix). لا ندعي تغطية جميع السيناريوهات المحتملة للعمل مع الجان بشكل كامل ، لكننا نأمل أن تكون المعلومات مفيدة في شكل كتاب مرجعي ومجموعة من الوصفات للمبرمجين والمهندسين العكسيين.
من المفهوم أن القارئ على المستوى الأساسي على دراية بتنسيق ELF (وإلا ، فإننا نوصي بسلسلة المقالات Executable and Linkable Format 101 ).
تحت الخفض ، سيتم سرد أدوات العمل والتقنيات الموصوفة لقراءة المعلومات الوصفية والتعديل والتحقق و تربية إنشاء الجان ، وكذلك روابط لمواد مفيدة.
"أنا أيضًا قزم ... أزرق باللون الأحمر ... الجان صبورون جدًا ... الأزرق باللون الأحمر ... ونحن الجان! ... باللون الأزرق باللون الأحمر ... لا يوجد سوى مشاكل من السحر ...
(ج) مملكة بن وهولي الصغيرة
الأدوات
في معظم الحالات ، يمكن تشغيل الأمثلة على كل من Linux و Windows.
في الوصفات ، سنستخدم الأدوات التالية:
- المرافق من مجموعة binutils (objcopy ، objdump ، readelf ، قطاع) ؛
- إطار radare2 ؛
- محرر hex مع دعم لقوالب الملفات (توضح الأمثلة 010Editor ، ولكن يمكنك استخدام ، على سبيل المثال ، Veles مجاني) ؛
- بيثون ومكتبة LIEF ؛
- أدوات مساعدة أخرى (الروابط في الوصفة).
الجان اختبار
ك "تجريبي" ، سوف نستخدم ملف ELF البسيط من مهمة PieIsMyFav من nutcake على crackmes.one ، لكن أي ممثل عن عائلة "Elven" سيفعل ذلك. إذا لم يتم العثور على الملف النهائي بالخصائص المطلوبة في المجال العام ، فسيتم توفير طريقة لإنشاء مثل هذا القزم.
الجان الحرة ويمكن الاطلاع أيضا على الروابط:
نوع الملف ، العنوان ، القسم
اعتمادًا على المهمة ، قد يكون ما يلي ذا أهمية:
- نوع الملف (DYN - مكتبة ، EXEC - قابل للتنفيذ ، RELOC - قابل للربط) ؛
- بنية الهدف (E_MACHINE - x86_64 ، x86 ، ARM ، إلخ) ؛
- نقطة إدخال التطبيق (نقطة الدخول) ؛
- قسم المعلومات.
010Editor
HEX Editor 010Editor يوفر نظام القوالب. بالنسبة لملفات ELF ، يُسمى القالب ، ELF.bt بشكل غريب ، ويقع في الفئة القابلة للتنفيذ (قوالب القائمة - قابلة للتنفيذ).
قد يكون موضع الاهتمام ، على سبيل المثال ، نقطة الإدخال إلى الملف القابل للتنفيذ (نقطة الإدخال) (المسجلة في رأس الملف).

readelf
يمكن اعتبار الأداة المساعدة readelf المعيار الفعلي للحصول على معلومات حول ملف ELF.
- قراءة رأس الملف:
$ readelf -h simple
نتيجة الفريق ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: DYN (Shared object file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x1070 Start of program headers: 64 (bytes into file) Start of section headers: 14800 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 11 Size of section headers: 64 (bytes) Number of section headers: 30 Section header string table index: 29
- قراءة المعلومات حول القطاعات والأقسام:
$ readelf -l -W simple
نتيجة الفريقلسهولة القراءة ، يتم تحويل العناوين إلى تنسيق 32 بت:
Elf file type is DYN (Shared object file) Entry point 0x1070 There are 11 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000040 0x00000040 0x00000040 0x000268 0x000268 R 0x8 INTERP 0x0002a8 0x000002a8 0x000002a8 0x00001c 0x00001c R 0x1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x000000 0x00000000 0x00000000 0x0005f8 0x0005f8 R 0x1000 LOAD 0x001000 0x00001000 0x00001000 0x00026d 0x00026d RE 0x1000 LOAD 0x002000 0x00002000 0x00002000 0x0001b8 0x0001b8 R 0x1000 LOAD 0x002de8 0x00003de8 0x00003de8 0x000258 0x000260 RW 0x1000 DYNAMIC 0x002df8 0x00003df8 0x00003df8 0x0001e0 0x0001e0 RW 0x8 NOTE 0x0002c4 0x000002c4 0x000002c4 0x000044 0x000044 R 0x4 GNU_EH_FRAME 0x002070 0x00002070 0x00002070 0x00003c 0x00003c R 0x4 GNU_STACK 0x000000 0x00000000 0x00000000 0x000000 0x000000 RW 0x10 GNU_RELRO 0x002de8 0x00003de8 0x00003de8 0x000218 0x000218 R 0x1 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 03 .init .plt .plt.got .text .fini 04 .rodata .eh_frame_hdr .eh_frame 05 .init_array .fini_array .dynamic .got .got.plt .data .bss 06 .dynamic 07 .note.ABI-tag .note.gnu.build-id 08 .eh_frame_hdr 09 10 .init_array .fini_array .dynamic .got
- قراءة معلومات القسم:
$ readelf -S -W simple
نتيجة الفريقلسهولة القراءة ، يتم تحويل العناوين إلى تنسيق 32 بت:
There are 30 section headers, starting at offset 0x39d0: Section Headers: [Nr] Name Type Address Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .interp PROGBITS 000002a8 0002a8 00001c 00 A 0 0 1 [ 2] .note.ABI-tag NOTE 000002c4 0002c4 000020 00 A 0 0 4 [ 3] .note.gnu.build-id NOTE 000002e4 0002e4 000024 00 A 0 0 4 [ 4] .gnu.hash GNU_HASH 00000308 000308 000024 00 A 5 0 8 [ 5] .dynsym DYNSYM 00000330 000330 0000d8 18 A 6 1 8 [ 6] .dynstr STRTAB 00000408 000408 0000a2 00 A 0 0 1 [ 7] .gnu.version VERSYM 000004aa 0004aa 000012 02 A 5 0 2 [ 8] .gnu.version_r VERNEED 000004c0 0004c0 000030 00 A 6 1 8 [ 9] .rela.dyn RELA 000004f0 0004f0 0000c0 18 A 5 0 8 [10] .rela.plt RELA 000005b0 0005b0 000048 18 AI 5 23 8 [11] .init PROGBITS 00001000 001000 000017 00 AX 0 0 4 [12] .plt PROGBITS 00001020 001020 000040 10 AX 0 0 16 [13] .plt.got PROGBITS 00001060 001060 000008 08 AX 0 0 8 [14] .text PROGBITS 00001070 001070 0001f2 00 AX 0 0 16 [15] .fini PROGBITS 00001264 001264 000009 00 AX 0 0 4 [16] .rodata PROGBITS 00002000 002000 000070 00 A 0 0 8 [17] .eh_frame_hdr PROGBITS 00002070 002070 00003c 00 A 0 0 4 [18] .eh_frame PROGBITS 000020b0 0020b0 000108 00 A 0 0 8 [19] .init_array INIT_ARRAY 00003de8 002de8 000008 08 WA 0 0 8 [20] .fini_array FINI_ARRAY 00003df0 002df0 000008 08 WA 0 0 8 [21] .dynamic DYNAMIC 00003df8 002df8 0001e0 10 WA 6 0 8 [22] .got PROGBITS 00003fd8 002fd8 000028 08 WA 0 0 8 [23] .got.plt PROGBITS 00004000 003000 000030 08 WA 0 0 8 [24] .data PROGBITS 00004030 003030 000010 00 WA 0 0 8 [25] .bss NOBITS 00004040 003040 000008 00 WA 0 0 1 [26] .comment PROGBITS 00000000 003040 00001c 01 MS 0 0 1 [27] .symtab SYMTAB 00000000 003060 000630 18 28 44 8 [28] .strtab STRTAB 00000000 003690 000232 00 0 0 1 [29] .shstrtab STRTAB 00000000 0038c2 000107 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), l (large) I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific)
- قراءة معلومات الرمز:
$ readelf -s -W simple
نتيجة الفريقيتم تقصير الإخراج لسهولة القراءة:
Symbol table '.dynsym' contains 9 entries: Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTable 2: 00000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2) 3: 00000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2) 4: 00000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2) 5: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 6: 00000000 0 FUNC GLOBAL DEFAULT UND __isoc99_scanf@GLIBC_2.7 (3) 7: 00000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable 8: 00000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (2) Symbol table '.symtab' contains 66 entries: Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 000002a8 0 SECTION LOCAL DEFAULT 1 2: 000002c4 0 SECTION LOCAL DEFAULT 2 3: 000002e4 0 SECTION LOCAL DEFAULT 3 4: 00000308 0 SECTION LOCAL DEFAULT 4 5: 00000330 0 SECTION LOCAL DEFAULT 5 6: 00000408 0 SECTION LOCAL DEFAULT 6 7: 000004aa 0 SECTION LOCAL DEFAULT 7 .... 26: 00000000 0 SECTION LOCAL DEFAULT 26 27: 00000000 0 FILE LOCAL DEFAULT ABS crtstuff.c 28: 000010a0 0 FUNC LOCAL DEFAULT 14 deregister_tm_clones 29: 000010d0 0 FUNC LOCAL DEFAULT 14 register_tm_clones 30: 00001110 0 FUNC LOCAL DEFAULT 14 __do_global_dtors_aux 31: 00004040 1 OBJECT LOCAL DEFAULT 25 completed.7389 ....
يلزم الخيار -W
لزيادة عرض إخراج وحدة التحكم (افتراضي ، 80 حرفًا).
ليف
يمكنك قراءة معلومات الرأس والمقطع باستخدام شفرة Python ومكتبة LIEF (توفر واجهة برمجة تطبيقات ليس فقط لـ Python):
import lief binary = lief.parse("simple.elf") header = binary.header print("Entry point: %08x" % header.entrypoint) print("Architecture: ", header.machine_type) for section in binary.sections: print("Section %s - size: %s bytes" % (section.name, section.size)
للحصول على معلومات حول المترجم والبناء ، راجع .comment
.note
.comment
و .comment
.
objdump
$ objdump -s --section .comment simple
نتيجة الفريق simple: file format elf64-x86-64 Contents of section .comment: 0000 4743433a 20284465 6269616e 20382e32 GCC: (Debian 8.2 0010 2e302d39 2920382e 322e3000 .0-9) 8.2.0.
readelf
$ readelf -p .comment simple
نتيجة الفريق String dump of section '.comment': [ 0] GCC: (Debian 8.2.0-9) 8.2.0
$ readelf -n simple
نتيجة الفريق Displaying notes found at file offset 0x000002c4 with length 0x00000020: Owner Data size Description GNU 0x00000010 NT_GNU_ABI_TAG (ABI version tag) OS: Linux, ABI: 3.2.0 Displaying notes found at file offset 0x000002e4 with length 0x00000024: Owner Data size Description GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring) Build ID: dae0509e4edb79719a65af37962b74e4cf2a8c2e
ليف
import lief binary = lief.parse("simple") comment = binary.get_section(".comment") print("Comment: ", bytes(comment.content))
سوف أحسب لك ... RPATH
يمكن أن يحفظ الجان المسارات للعثور على مكتبات متصلة ديناميكيًا. لكي لا تضبط متغير النظام LD_LIBRARY_PATH
قبل بدء التطبيق ، يمكنك ببساطة "تضمين" هذا المسار في ملف ELF.
للقيام بذلك ، استخدم الإدخال الموجود في القسم .dynamic
بالنوع DT_RPATH
أو DT_RUNPATH
(انظر الفصل " DT_RUNPATH
التي تم البحث عنها بواسطة رابط رابط وقت التشغيل" في الوثائق).
وكن حذرًا ، المطور الصغير ، لا "تنام" دليل مشروعك!
كيف تظهر RPATH؟
السبب الرئيسي لظهور سجل RPATH في قزم هو خيار رابط -rpath
للبحث عن مكتبة ديناميكية. شيء مثل هذا:
$ gcc -L./lib -Wall -Wl,-rpath=/run/media/pablo/disk1/projects/cheat_sheets/ELF/lib/ -o test_rpath.elf bubble_main.c -lbubble
سيقوم هذا الأمر بإنشاء سجل RPATH في القسم .dynamic
مع القيمة /run/media/pablo/disk1/projects/cheat_sheets/ELF/lib/
.
readelf
يمكنك عرض العناصر من القسم .dynamic
(من بينها RPATH) كما يلي:
$ readelf -d test_rpath.elf
نتيجة الفريقلسهولة القراءة ، يتم تقصير نتيجة الأمر:
Dynamic section at offset 0x2dd8 contains 28 entries: Tag Type Name/Value 0x0000000000000001 (NEEDED) Shared library: [libbubble.so] 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] 0x000000000000000f (RPATH) Library rpath: [/run/media/pablo/disk1/projects/cheat_sheets/ELF/lib/] 0x000000000000000c (INIT) 0x1000 0x000000000000000d (FINI) 0x11c8 ....
ليف
باستخدام مكتبة LIEF ، يمكنك أيضًا قراءة سجل RPATH في العفريت:
import lief from lief.ELF import DYNAMIC_TAGS elf = lief.parse("test_rpath.elf") if elf.has(DYNAMIC_TAGS.RPATH): rpath = next(filter(lambda x: x.tag == DYNAMIC_TAGS.RPATH, elf.dynamic_entries)) for path in rpath.paths: print(path) else: print("No RPATH in ELF")
اقرأ عن القسم الديناميكي
التحقق من قزم للأمن
لم يتم تحديث البرنامج النصي للتحقق من الأمان checkec.sh من الباحث توبياس كلاين (مؤلف يوميات بوغ هانتر ) منذ عام 2011. يتحقق البرنامج النصي لملف ELF من توفر الخيارات RelRO (للقراءة فقط لإعادة التوطين) ، NX (مكدس غير قابل للتنفيذ) ، Stack Canaries ، PIE (Position Independent Executables) ويستخدم الأداة المساعدة readelf لعملها.
ليف
يمكنك جعل التناظرية الخاصة بك على ركبة Python و LIEF (أقصر قليلاً من السلف ومع التحقق الإضافي من خيار كود منفصل ):
import lief from lief.ELF import DYNAMIC_TAGS, SEGMENT_TYPES def filecheck(filename): binary = lief.parse(filename)
Radare2
بفضل dukebarman لإضافة استخدام Radare2 لعرض معلومات مشابهة لـ checkec :
> r2 -ci~pic,canary,nx,crypto,stripped,static,relocs test_stack_proteck
كود الخام من العفريت (ثنائي من ELF)
هناك حالات عندما لا تكون هناك حاجة إلى "ملابس elven" في شكل بنية ELF ، ولكن هناك حاجة فقط لرمز التطبيق القابل للتنفيذ "العاري".
objcopy
قد يكون استخدام objcopy مألوفًا لأولئك الذين يكتبون البرامج الثابتة:
$ objcopy -O binary -S -g simple.elf simple.bin
-S
- لحذف معلومات الشخصية.-g
- لإزالة معلومات التصحيح.
ليف
لا السحر. فقط خذ محتويات المقاطع التي تم تحميلها وقم بإنشاء binar منها:
import lief from lief.ELF import SECTION_FLAGS, SECTION_TYPES binary = lief.parse("test") end_addr = 0 data = [] for section in filter(lambda x: x.has(SECTION_FLAGS.ALLOC) and x.type != SECTION_TYPES.NOBITS, binary.sections): if 0 < end_addr < section.virtual_address: align_bytes = b'\x00' * (section.virtual_address - end_addr) data.append(align_bytes) data.append(bytes(section.content)) end_addr = section.virtual_address + section.size with open('test.lief.bin', 'wb') as f: for d_bytes in data: f.write(d_bytes)
أسماء وظيفة منغم - demangled
في ELFs التي تم إنشاؤها من كود C ++ ، يتم تزيين أسماء الوظائف (مشوهة) لتبسيط البحث عن وظيفة الفئة المقابلة. ومع ذلك ، فإن قراءة هذه الأسماء في التحليل ليست مريحة للغاية.
اختبار قزم
نانومتر
لتمثيل الأسماء في نموذج قابل للقراءة ، يمكنك استخدام الأداة المساعدة nm من مجموعة binutils:
ليف
عرض أسماء الرموز في نموذج demangulated باستخدام مكتبة LIEF:
import lief binary = lief.parse("demangle-test-cpp") for symb in binary.symbols: print(symb.name, symb.demangled_name)
التجمع ، وتسجيل ، وتعديل قزم
بعد تصحيح التطبيق وإصداره في العالم المتوحش ، من المنطقي إزالة معلومات التعريف:
- أقسام التصحيح - عديمة الفائدة في معظم الحالات ؛
- أسماء المتغيرات والوظائف - لا تؤثر مطلقًا على أي شيء للمستخدم النهائي (يعمل على تعقيد الاتجاه العكسي قليلاً) ؛
- جدول القسم - لا حاجة مطلقًا لتشغيل التطبيق (لن يؤدي الافتقار إلى تعقيد الاتجاه المعاكس قليلاً).
معلومات الحرف هي أسماء الكائنات والوظائف. بدون ذلك ، يكون عكس التطبيق أكثر تعقيدًا.
قطاع
في أبسط الحالات ، يمكنك استخدام الأداة المساعدة قطاع من مجموعة binutils. لحذف جميع المعلومات الشخصية ، فقط قم بتشغيل الأمر:
sstrip
لإزالة معلومات الأحرف بعناية (بما في ذلك صفر بايت غير ضرورية في نهاية الملف) ، يمكنك استخدام الأداة المساعدة sstrip من مجموعة ELFkickers . لحذف جميع المعلومات الشخصية ، فقط قم بتشغيل الأمر:
$ sstrip -z simple
ليف
باستخدام مكتبة LIEF ، يمكنك أيضًا إنشاء شريط سريع (يتم حذف جدول الرموز - قسم .symtab
):
import lief binary = lief.parse("simple") binary.strip() binary.write("simple.stripped")
حذف جدول التقسيم
كما ذكر أعلاه ، فإن وجود / عدم وجود جدول القسم لا يؤثر على تشغيل التطبيق. ولكن في نفس الوقت ، وبدون جدول القسم ، يصبح عكس التطبيق أكثر تعقيدًا.
سوف نستخدم مكتبة LIEF تحت Python ومثال حذف جدول القسم :
import lief binary = lief.parse("simple") binary.header.numberof_sections = 0 binary.header.section_header_offset = 0 binary.write("simple.modified")
تعديل وحذف RPATH
تشرباث ، باتشلف
لتغيير RPATH ضمن Linux ، يمكنك استخدام الأدوات المساعدة chrpath (المتوفرة في معظم التوزيعات) أو PatchELF .
تغيير RPATH:
$ chrpath -r /opt/my-libs/lib:/foo/lib test_rpath.elf
أو
$ patchelf --set-rpath /opt/my-libs/lib:/foo/lib test_rpath.elf
إزالة RPATH:
$ chrpath -d test_rpath.elf
أو
$ patchelf --shrink-rpath test_rpath.elf
ليف
تسمح لك مكتبة LIEF أيضًا بتعديل وحذف سجل RPATH.
تغيير RPATH:
import lief binary = lief.parse("test_rpath.elf") rpath = next(filter(lambda x: x.tag == lief.ELF.DYNAMIC_TAGS.RPATH, binary.dynamic_entries)) rpath.paths = ["/opt/my-lib/here"] binary.write("test_rpath.patched")
إزالة RPATH:
import lief binary = lief.parse("test_rpath.elf") binary.remove(lief.ELF.DYNAMIC_TAGS.RPATH) binary.write("test_rpath.patched")
لتعقيد عكس التطبيق ، يمكنك حفظ المعلومات الرمزية ، ولكن تخلط بين أسماء الكائنات. نستخدم قزم crackme01_32bit من crackme01 بواسطة seveb كموضوع اختبار.
قد تبدو النسخة المبسطة لمثال من مكتبة LIEF كما يلي:
import lief binary = lief.parse("crackme01_32bit") for i, symb in enumerate(binary.static_symbols): symb.name = "zzz_%d" % i binary.write("crackme01_32bit.obfuscated")
نتيجة لذلك ، نحصل على:
$ readelf -s crackme01_32bit.obfuscated ... Symbol table '.symtab' contains 78 entries: Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND zzz_0 1: 08048154 0 SECTION LOCAL DEFAULT 1 zzz_1 2: 08048168 0 SECTION LOCAL DEFAULT 2 zzz_2 3: 08048188 0 SECTION LOCAL DEFAULT 3 zzz_3 4: 080481ac 0 SECTION LOCAL DEFAULT 4 zzz_4 5: 080481d0 0 SECTION LOCAL DEFAULT 5 zzz_5 6: 080482b0 0 SECTION LOCAL DEFAULT 6 zzz_6 7: 0804835a 0 SECTION LOCAL DEFAULT 7 zzz_7 8: 08048378 0 SECTION LOCAL DEFAULT 8 zzz_8 9: 080483b8 0 SECTION LOCAL DEFAULT 9 zzz_9 10: 080483c8 0 SECTION LOCAL DEFAULT 10 zzz_10 ...
استبدال وظيفة عبر PLT / GOT
المعروف أيضًا باسم ELF PLT Infection .
لكي لا تقوم بنسخ اللصق ، فقط اترك الروابط في الموضوع:
تغيير نقطة الدخول
يمكن أن يكون مفيدًا عند إنشاء تصحيحات أو تثبيت الخطافات والأجهزة الديناميكية الأخرى أو عند استدعاء الوظائف المخفية. كتجربة ، نستخدم قزم crackme01_32bit من crackme01 بواسطة seveb
radare2
يبدأ radare2 في وضع التسجيل (الخيار -w
) - سيتم إجراء تغييرات على الملف الأصلي:
$ ./crackme01_32bit Please enter the secret number: ^C $ r2 -w -nn crackme01_32bit [0x00000000]> .pf.elf_header.entry=0x0804860D [0x00000000]> q $ ./crackme01_32bit Nope.
ليف
import lief binary = lief.parse("crackme01_32bit") header = binary.header header.entrypoint = 0x0804860D binary.write("crackme01_32bit.patched")
رمز التصحيح
كاختبار بسيط ، خذ crackmepal لـ novn91 مع الكراك . عند بدء تشغيله بدون معلمات ، يعرض البرنامج:
$ ./crackmeMario usage <password>
عند تشغيله باستخدام معلمة سلسلة عشوائية ، فإنه يعرض:
./crackmeMario qwerty try again pal.
سنقوم بعمل تصحيح حتى يعرض البرنامج فور بدء التشغيل رسالة "وظيفة جيدة! الآن كجن لي! "
radare2
يمكن radare2 تصحيح أي التنسيقات التي يدعمها نفسه. في هذه الحالة ، من الممكن وصف التصحيحات بتنسيق نصي:
# Rapatch for https://crackmes.one/crackme/5ccecc7e33c5d4419da559b3 !echo Patching crackme 0x115D : jmp 0x1226
يمكنك تطبيق مثل هذا التصحيح باستخدام الأمر:
$ r2 -P patch.txt crackmeMario
اقرأ عن كود التصحيح من خلال radare2:
ليف
LIEF يسمح لك بتصحيح قزم (الكتابة فوق بايت) على العنوان الظاهري المحدد. يمكن أن يكون التصحيح في شكل صفيف بايت أو كقيمة عدد صحيح:
import lief binary = lief.parse("crackmeMario") binary.patch_address(0x115D, bytearray(b"\xe9\xc4\x00\x00\x00")) binary.write("crackmeMario.patched")
بعد تطبيق التصحيح ، سيتم إخراج البرنامج:
$ ./crackmeMario.patched good job! now keygen me!
إضافة قسم إلى ELF
objcopy
يسمح لك objcopy بإضافة قسم ، لكن هذا القسم لن ينتمي إلى أي شريحة ولن يتم تحميله في ذاكرة الوصول العشوائي عند بدء تشغيل التطبيق:
$ objcopy --add-section .testme=data.zip \ --set-section-flags .testme=alloc,contents,load,readonly \ --change-section-address .testme=0x08777777 \ simple simple.patched.elf
ليف
تتيح لك مكتبة LIEF إضافة قسم جديد وقطاعه المقابل ( loaded=True
علامة loaded=True
) إلى ELF موجود:
import lief binary = lief.parse("simple") data = bytearray(b"\xFF" * 16) section = lief.ELF.Section(".testme", lief.ELF.SECTION_TYPES.PROGBITS) section += lief.ELF.SECTION_FLAGS.EXECINSTR section += lief.ELF.SECTION_FLAGS.ALLOC section.content = data binary.add(section, loaded=True) binary.write("simple.testme.lief")
تغيير القسم
objcopy
يسمح لك objcopy باستبدال محتويات القسم ببيانات من ملف ، بالإضافة إلى تغيير العنوان الظاهري للقسم والأعلام:
$ objcopy --update-section .testme=patch.bin \ --change-section-address .testme=0x08999999 simple simple.testme.elf
ليف
import lief binary = lief.parse("simple") data = bytearray(b"\xFF" * 17) section = binary.get_section(".text") section.content = data binary.write("simple.patched")
حذف القسم
objcopy
يسمح لك objcopy بحذف قسم معين بالاسم:
$ objcopy --remove-section .testme simple.testme.elf simple.no_testme.elf
ليف
يبدو حذف قسم باستخدام مكتبة LIEF كما يلي:
import lief binary = lief.parse("simple.testme.elf") binary.remove_section(".testme") binary.write("simple.no_testme")
حاوية قزم
الوصفة مستوحاة من Gremlins و ELF magic: ماذا لو كان ملف ELF عبارة عن حاوية؟ . يوجد أيضًا رجل حول أداة elfwrap الأصل من Solaris ، والذي يسمح لك بإنشاء ملف ELF من البيانات التعسفية ، ويستخدم تنسيق ELF ببساطة كحاوية.
دعونا نحاول أن نفعل الشيء نفسه في بيثون و LIEF.
لسوء الحظ ، في الوقت الحالي ، لا تستطيع مكتبة LIEF إنشاء ملف قزم من البداية ، لذلك تحتاج إلى مساعدته - إنشاء قالب ELF فارغ:
$ echo "" | gcc -m32 -fpic -o empty.o -c -xc - $ gcc -m32 -shared -o libempty.so empty.o
يمكنك الآن استخدام هذا القالب لتعبئة البيانات:
import lief binary = lief.parse("libempty.so") filename = "crackme.zip" data = open(filename, 'rb').read()
العفريت "مع مقطورة"
لا يفرض تنسيق ELF قيودًا على البيانات الموجودة في الملف ، لكنه لا ينتمي إلى أي مقطع. وبالتالي ، من الممكن إنشاء ملف قابل للتنفيذ ، والذي سيتم تخزينه بعد بنية ELF. هذا شيء لن يتم تحميله في ذاكرة الوصول العشوائي في وقت التشغيل ، ولكن سيتم كتابته على القرص ، وفي أي وقت يمكن قراءته من القرص.
- لن تفكر IDA Pro في هذه البيانات عند التحليل
مثال على هيكل الملف مع مقطورة

radare2
يمكن تأسيس وجود "مقطورة" من خلال مقارنة أحجام الملفات الحقيقية والمحسوبة:
$ radare2 test.elf [0x00001040]> ?v $s 0x40c1 [0x00001040]> iZ 14699
readelf
لا يعرض readelf معلومات حول وجود "مقطورة" ، ولكن يمكن حسابها يدويًا:
$ ls -l test.elf
ليف
تسمح لك مكتبة LIEF بالتحقق من وجود "مقطورة" وإضافتها. باستخدام LIEF ، يبدو كل شيء مقتضبًا تمامًا:
import lief binary = lief.parse("test")
الفراغ العفريت (قزم من الصفر)
على الإنترنت ، يمكنك العثور على مشاريع لإنشاء ملف ELF "يدويًا" - دون استخدام مترجم ورابط تحت الاسم العام "ELF from scratch":
الإلمام بهذه المشاريع له تأثير مفيد على امتصاص شكل ELF.
أصغر قزم
تجارب مثيرة للاهتمام مع تقليل حجم قزم موضحة في المقالات:
باختصار ، لا يستخدم أداة التحميل elf في نظام التشغيل جميع حقول الرأس وجداول القطع ، ويمكن وضع بعض التعليمات البرمجية القابلة للتنفيذ مباشرة في بنية رأس ELF (يتم أخذ الرمز من المقالة الأولى):
; tiny.asm BITS 32 org 0x00010000 db 0x7F, "ELF" ; e_ident dd 1 ; p_type dd 0 ; p_offset dd $$ ; p_vaddr dw 2 ; e_type ; p_paddr dw 3 ; e_machine dd _start ; e_version ; p_filesz dd _start ; e_entry ; p_memsz dd 4 ; e_phoff ; p_flags _start: mov bl, 42 ; e_shoff ; p_align xor eax, eax inc eax ; e_flags int 0x80 db 0 dw 0x34 ; e_ehsize dw 0x20 ; e_phentsize db 1 ; e_phnum ; e_shentsize ; e_shnum ; e_shstrndx filesize equ $ - $$
تجميع والحصول على ELF من الحجم ... 45 بايت :
$ nasm -f bin -o a.out tiny.asm $ chmod +x a.out $ ./a.out ; echo $? 42 $ wc -c a.out 45 a.out
نمط قزم
لإنشاء قزم باستخدام مكتبة LIEF ، يمكنك اتخاذ الخطوات التالية (راجع الوصفة "Elf-container"):
- تأخذ ملف ELF بسيط كقالب ؛
- استبدال محتويات الأقسام ، إضافة أقسام جديدة ؛
- تكوين المعلمات اللازمة (نقطة الدخول والأعلام).
بدلا من الاستنتاج
إضافة إلى المقال ، وجدنا أنه تحول إلى شيء يشبه قصيدة لمكتبة LIEF. لكن هذا لم يكن مخططًا له - أردت أن أوضح كيفية التعامل مع ملفات ELF باستخدام أدوات مختلفة.
بالتأكيد هناك أو تحتاج البرامج النصية التي لم يتم ذكرها هنا - اكتب عن هذا في التعليقات.
المراجع والأدب