إنشاء حزمة ملف x86_64 ELF لنظام التشغيل linux

مقدمة


سوف يصف هذا المنشور إنشاء حزمة ملفات قابلة للتنفيذ بسيطة لنظام التشغيل Linux x86_64. من المفترض أن يكون القارئ معتادًا على لغة البرمجة C ولغة التجميع للهيكل x86_64 وملفات ELF الخاصة بالجهاز. لضمان الوضوح ، تمت إزالة معالجة الأخطاء من التعليمات البرمجية في المقالة ولم يتم عرض تطبيقات بعض الوظائف ، ويمكن العثور على الرمز الكامل بالنقر فوق الارتباطات إلى github ( loader ، packer ).

الفكرة هي: ننقل ملف ELF إلى باكر ، ونحصل على ملف جديد بالهيكل التالي عند الإخراج:
رأس ELF
عنوان البرنامج
شريحة الكودحزم ملف تنزيل ELF
معبأة ملف ELF
256 بايت من البيانات العشوائية

للضغط ، تقرر استخدام خوارزمية Huffman ، للتشفير - AES-CTR مع مفتاح 256 بت ، أي التنفيذ من kokke tiny-AES-c . يتم استخدام 256 بايت من البيانات العشوائية لتهيئة مفتاح AES وناقل التهيئة باستخدام مولد الأرقام العشوائية الزائفة ، كما هو موضح أدناه:

for(int i = 0; i < 32; i++) { seed = (1103515245*seed + 12345) % 256; key[i] = buf[seed]; } 

كان هذا القرار بسبب الرغبة في تعقيد الهندسة العكسية. حتى الآن ، أدركت أن المضاعفات غير ذات أهمية ، لكنني لم أبدأ في إزالتها ، حيث أنني لم أرغب في قضاء الوقت والطاقة في ذلك.

محمل


أولاً ، سيتم النظر في عمل أداة تحميل التشغيل. يجب ألا يحتوي المُحمل على أي تبعيات ، لذلك يجب كتابة جميع الوظائف الضرورية من مكتبة C القياسية بشكل مستقل (تنفيذ هذه الوظائف متاحًا بالرجوع ). كما يجب أن تكون مستقلة موضعيًا.

وظيفة _Start


يبدأ محمل الإقلاع من وظيفة _start ، التي تنقل ببساطة argc و argv إلى main:

 .extern main .globl _start .text _start: movq (%rsp), %rdi movq %rsp, %rsi addq $8, %rsi call main 

الوظيفة الرئيسية


يبدأ ملف main.c بتعريف العديد من المتغيرات الخارجية:

 extern void* loader_end; //    , .   //  ELF . extern size_t payload_size; //   ELF  extern size_t key_seed; //     // -   . extern size_t iv_seed; //     // -     

تم إعلانها جميعًا على أنها خارجية من أجل العثور على موضع الأحرف المطابقة للمتغيرات (Elf64_Sym) في الحزمة وتغيير قيمها.

الوظيفة الرئيسية نفسها بسيطة جدا. تتمثل الخطوة الأولى في تهيئة مؤشرات إلى ملف ELF محزوم ، ومخزن مؤقت 256 بايت ، وإلى الجزء العلوي من المكدس. ثم يتم فك تشفير ملف ELF وتوسيعه ، ثم يتم وضعه في المكان المناسب في الذاكرة باستخدام دالة load_elf ، وأخيراً ، تعود قيمة سجل rsp إلى حالته الأصلية ، وتحدث قفزة إلى نقطة إدخال البرنامج:

 #define SET_STACK(sp) __asm__ __volatile__ ("movq %0, %%rsp"::"r"(sp)) #define JMP(addr) __asm__ __volatile__ ("jmp *%0"::"r"(addr)) int main(int argc, char **argv) { uint8_t *payload = (uint8_t*)&loader_end; //    // ELF  uint8_t *entropy_buf = payload + payload_size; //   256- //  void *rsp = argv-1; //     struct AES_ctx ctx; AES_init_ctx_iv(&ctx, entropy_buf, key_seed, iv_seed); //  AES AES_CTR_xcrypt_buffer(&ctx, payload, payload_size); //  ELF memset(&ctx, 0, sizeof(ctx)); //   AES size_t decoded_payload_size; //  ELF char *decoded_payload = huffman_decode((char*)payload, payload_size, &decoded_payload_size); //     ELF  , //   ET_EXEC  NULL. void *load_addr = elf_load_addr(rsp, decoded_payload, decoded_payload_size); load_addr = load_elf(load_addr, decoded_payload); //  ELF  , //    //  . memset(decoded_payload, 0, decoded_payload_size); //   ELF munmap(decoded_payload, decoded_payload_size); //   //    //  ELF     AES AES_init_ctx_iv(&ctx, entropy_buf, key_seed, iv_seed); AES_CTR_xcrypt_buffer(&ctx, payload, payload_size); memset(&ctx, 0, sizeof(ctx)); SET_STACK(rsp); //    JMP(load_addr); //       } 

تتم إعادة تعيين حالة AES وملف ELF الذي تم فك ضغطه لأغراض أمنية - بحيث يتم تخزين البيانات الرئيسية وفك تشفيرها في الذاكرة فقط لوقت الاستخدام.

بعد ذلك ، سننظر في تنفيذ بعض الوظائف.

load_elf


أخذت هذه الوظيفة من مستخدم github الذي يحمل اسم bediger من مستودع userlandexec الخاص به ووضع اللمسات الأخيرة عليه ، لأن الوظيفة الأصلية تعطلت على ملفات مثل ET_DYN. حدث الفشل بسبب حقيقة أن قيمة الوسيطة الأولى لاستدعاء نظام mmap قد تم تعيينها على NULL ، وتم إرجاع العنوان قريبًا جدًا من البرنامج الرئيسي ، أثناء إجراء مكالمات لاحقة ل mmap ونسخ المقاطع إلى العناوين التي تم إرجاعها إليها ، وتم استبدال رمز البرنامج الرئيسي ، وحدثت التعليمة البرمجية للبرنامج الرئيسي. لذلك ، تقرر إضافة عنوان البداية كمعلمة إلى وظيفة load_elf. تعمل الوظيفة نفسها عبر جميع رؤوس البرامج ، وتخصص الذاكرة (يجب أن يكون رقمها مضاعفًا لحجم الصفحة) لقطاعات PT_LOAD من ملف ELF ، ونسخ محتوياتها إلى مناطق الذاكرة المخصصة وتعيين حقوق القراءة والكتابة والتنفيذ المناظرة لهذه المناطق:

 //      #define PAGEUP(x) (((unsigned long)x + 4095)&(~4095)) //      #define PAGEDOWN(x) ((unsigned long)x&(~4095)) void* load_elf(void *load_addr, void *mapped) { Elf64_Ehdr *ehdr = mapped; Elf64_Phdr *phdr = mapped + ehdr->e_phoff; void *text_segment = NULL; unsigned long initial_vaddr = 0; unsigned long brk_addr = 0; for(size_t i = 0; i < ehdr->e_phnum; i++, phdr++) { unsigned long rounded_len, k; void *segment; //   PT_LOAD,    if(phdr->p_type != PT_LOAD) continue; if(text_segment != 0 && ehdr->e_type == ET_DYN) { //  ET_DYN phdr->p_vaddr    , //        //    ,      //     load_addr = text_segment + phdr->p_vaddr - initial_vaddr; load_addr = (void*)PAGEDOWN(load_addr); } else if(ehdr->e_type == ET_EXEC) { //  ET_EXEC phdr->p_vaddr     load_addr = (void*)PAGEDOWN(phdr->p_vaddr); } //        rounded_len = phdr->p_memsz + (phdr->p_vaddr % 4096); rounded_len = PAGEUP(rounded_len); //        segment = mmap(load_addr, rounded_len, PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); if(ehdr->e_type == ET_EXEC) load_addr = (void*)phdr->p_vaddr; else load_addr = segment + (phdr->p_vaddr % 4096); //         memcpy(load_addr, mapped + phdr->p_offset, phdr->p_filesz); if(!text_segment) { text_segment = segment; initial_vaddr = phdr->p_vaddr; } unsigned int protflags = 0; if(phdr->p_flags & PF_R) protflags |= PROT_READ; if(phdr->p_flags & PF_W) protflags |= PROT_WRITE; if(phdr->p_flags & PF_X) protflags |= PROT_EXEC; mprotect(segment, rounded_len, protflags); //   // , ,  k = phdr->p_vaddr + phdr->p_memsz; if(k > brk_addr) brk_addr = k; } if (ehdr->e_type == ET_EXEC) { brk(PAGEUP(brk_addr)); //  ET_EXEC ehdr->e_entry     load_addr = (void*)ehdr->e_entry; } else { //  ET_DYN ehdr->e_entry    , //           load_addr = (void*)ehdr + ehdr->e_entry; } return load_addr; //       } 

elf_load_addr


تقوم هذه الدالة لملفات ET_EXEC ELF بإرجاع NULL ، نظرًا لأن الملفات من هذا النوع يجب أن تكون موجودة في العناوين المحددة فيها. بالنسبة إلى ملفات ET_DYN ، يتم أولاً حساب العنوان الذي يساوي الفرق بين العنوان الأساسي للبرنامج الرئيسي (أي محمل الإقلاع) ، ومقدار الذاكرة المطلوبة لوضع ELF في الذاكرة ، و 4096 ، 4096 - الفجوة اللازمة حتى لا يتم وضع ملف ELF بجوار البرنامج الرئيسي مباشرةً. بعد حساب هذا العنوان ، يتم التحقق مما إذا كانت منطقة الذاكرة تتقاطع ، من العنوان المحدد إلى العنوان الأساسي للبرنامج الرئيسي ، مع المنطقة من بداية ملف ELF غير المعبأ إلى نهايته. في حالة التقاطع ، يتم إرجاع العنوان مساويًا للفرق بين عنوان البدء لعنصر ELF غير المعبأ وكمية الذاكرة المطلوبة لوضعه ، وإلا يتم إرجاع العنوان المحسوب مسبقًا.

تم العثور على العنوان الأساسي للبرنامج عن طريق استخراج عنوان رؤوس البرنامج من المتجه المساعد (متجه ELF المساعد) ، والذي يقع بعد المؤشرات إلى متغيرات البيئة في المكدس ، وطرح حجم رأس ELF منه:

       ---------------------------------------------------------------------------    -> [ argc ] 8 [ argv[0] ] 8 [ argv[1] ] 8 [ argv[..] ] 8 * x [ argv[n – 1] ] 8 [ argv[n] ] 8 (= NULL) [ envp[0] ] 8 [ envp[1] ] 8 [ envp[..] ] 8 [ envp[term] ] 8 (= NULL) [ auxv[0] (Elf64_auxv_t) ] 16 [ auxv[1] (Elf64_auxv_t) ] 16 [ auxv[..] (Elf64_auxv_t) ] 16 [ auxv[term] (Elf64_auxv_t) ] 16 (= AT_NULL) [  ] 0 - 16 [    ] >= 0 [   ] >= 0 [   ] 8 (= NULL) <    > 0 --------------------------------------------------------------------------- 

الهيكل الذي وصف به كل عنصر من عناصر المتجه المساعد له الشكل:

 typedef struct { uint64_t a_type; //   union { uint64_t a_val; //  } a_un; } Elf64_auxv_t; 

إحدى قيم a_type الصالحة هي AT_PHDR ، ثم يشير a_val إلى رؤوس البرنامج. التالي هو رمز الدالة elf_load_addr:

 void* elf_base_addr(void *rsp) { void *base_addr = NULL; unsigned long argc = *(unsigned long*)rsp; char **envp = rsp + (argc+2)*sizeof(unsigned long); //    //   while(*envp++); //        Elf64_auxv_t *aux = (Elf64_auxv_t*)envp; //    //  for(; aux->a_type != AT_NULL; aux++) { //        if(aux->a_type == AT_PHDR) { //   ELF ,     //      base_addr = (void*)(aux->a_un.a_val – sizeof(Elf64_Ehdr)); break; } } return base_addr; } size_t elf_memory_size(void *mapped) { Elf64_Ehdr *ehdr = mapped; Elf64_Phdr *phdr = mapped + ehdr->e_phoff; size_t mem_size = 0, segment_len; for(size_t i = 0; i < ehdr->e_phnum; i++, phdr++) { if(phdr->p_type != PT_LOAD) continue; segment_len = phdr->p_memsz + (phdr->p_vaddr % 4096); mem_size += PAGEUP(segment_len); } return mem_size; } void* elf_load_addr(void *rsp, void *mapped, size_t mapped_size) { Elf64_Ehdr *ehdr = mapped; if(ehdr->e_type == ET_EXEC) return NULL; size_t mem_size = elf_memory_size(mapped) + 0x1000; void *load_addr = elf_base_addr(rsp); if(mapped < load_addr && mapped + mapped_size > load_addr - mem_size) load_addr = mapped; return load_addr - mem_size; } 

وصف البرنامج النصي رابط


من الضروري تحديد الأحرف للمتغيرات الخارجية الموضحة أعلاه ، وتأكد أيضًا من أن بيانات الكود والمحمل بعد التحويل في نفس القسم .text. يعد هذا ضروريًا لاستخراج رمز آلة التحميل بسهولة من خلال الاستغناء عن محتويات هذا القسم من الملف. لتحقيق هذه الأهداف ، تم كتابة البرنامج النصي رابط التالي:

 ENTRY(_start) SECTIONS { . = 0; .text :{ *(.text) *(.text.startup) *(.data) *(.rodata) payload_size = .; QUAD(0) key_seed = .; QUAD(0) iv_seed = .; QUAD(0) loader_end = .; } } 

تجدر الإشارة إلى أن QUAD (0) تضع 8 بايتات من الأصفار ، بدلاً من ذلك يقوم البديل باستبدال قيم محددة. لقص رمز الجهاز ، تمت كتابة أداة صغيرة تكتب أيضًا في بداية رمز الجهاز تحول نقطة الإدخال إلى أداة تحميل التشغيل من بداية أداة تحميل التشغيل ، وإزاحة قيم أحرف payload_size ، و key_seed و iv_seed من بداية أداة تحميل bootloader. رمز هذه الأداة متاح هنا . هذا ينتهي وصف بووتلوأدر.

باكر مباشرة


النظر في الوظيفة الرئيسية للباكر. يستخدم وسيطين لسطر الأوامر: اسم ملف الإدخال هو argv [1] واسم ملف الإخراج هو argv [2]. أولاً ، يتم عرض ملف الإدخال في الذاكرة والتحقق من توافقه مع باكر. يعمل الرابط مع نوعين فقط من ملفات ELF: ET_EXEC و ET_DYN ، وفقط مع الملفات المترجمة بشكل ثابت. كان سبب إدخال هذا التقييد هو حقيقة أن أنظمة linux المختلفة لها إصدارات مختلفة من المكتبات المشتركة ، أي احتمال عدم تشغيل برنامج مترجم ديناميكيًا على نظام آخر غير النظام الأصلي كبير جدًا. الكود المقابل في الوظيفة الرئيسية:

 size_t mapped_size; void *mapped = map_file(argv[1], &mapped_size); if(check_elf(mapped) < 0) return 1; 

بعد ذلك ، إذا نجح ملف الإدخال في التحقق من التوافق ، فسيتم ضغطه:

 size_t comp_size; uint8_t *comp_buf = huffman_encode(mapped, &comp_size); 

بعد ذلك ، يتم إنشاء حالة AES ، ويتم تشفير ملف ELF المضغوط. يتم تحديد حالة AES بالهيكل التالي:

 #define AES_ENTROPY_BUFSIZE 256 typedef struct { uint8_t entropy_buf[AES_ENTROPY_BUFSIZE]; // 256-  size_t key_seed; //      size_t iv_seed; //       struct AES_ctx ctx; //  AES-CTR } AES_state_t; 

كود المقابلة في الرئيسي:

 AES_state_t aes_st; for(int i = 0; i < AES_ENTROPY_BUFSIZE; i++) state.entropy_buf[i] = rand() % 256; state.key_seed = rand(); state.iv_seed = rand(); AES_init_ctx_iv(&state.ctx, state.entropy_buf, state.key_seed, state.iv_seed); AES_CTR_xcrypt_buffer(&aes_st.ctx, comp_buf, comp_size); 

بعد ذلك ، تتم تهيئة البنية التي تخزّن معلومات حول أداة تحميل التشغيل ، ويتم تغيير قيم الحمولة_الحجمية ، و key_seed ، و iv_seed في محمل الإقلاع- إلى تلك التي تم إنشاؤها في الخطوة السابقة ، وبعد ذلك تتم إعادة تعيين حالة AES. يتم تخزين معلومات حول أداة تحميل التشغيل في البنية التالية:

 typedef struct { char *loader_begin; //      size_t entry_offset; //       size_t *payload_size_patch_offset; //     // ELF    size_t *key_seed_pacth_offset; //     //       size_t *iv_seed_patch_offset; //     //     //    size_t loader_size; //     } loader_t; 

كود المقابلة في الرئيسي:

 loader_t loader; init_loader(&loader); *loader.payload_size_patch_offset = comp_size; *loader.key_seed_pacth_offset = aes_st.key_seed; *loader.iv_seed_patch_offset = aes_st.iv_seed; memset(&aes_st.ctx, 0, sizeof(aes_st.ctx)); 

في الجزء الأخير ، نقوم بإنشاء ملف إخراج ، وكتابة رأس ELF ، ورأس برنامج واحد ، ورمز محمل ، وملف ELF مضغوط ومشفّر ، ومخزن مؤقت 256 بايت فيه:

 int out_fd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0755); //  //   write_elf_ehdr(out_fd, &loader); //  ELF  write_elf_phdr(out_fd, &loader, comp_size); //    write(out_fd, loader.loader_begin, loader.loader_size); //   write(out_fd, comp_buf, comp_size); //     ELF write(out_fd, aes_st.entropy_buf, AES_ENTROPY_BUFSIZE); //  // 256-  

ينتهي الرمز الرئيسي للباكر هنا ، ثم سيتم النظر في الوظائف التالية: وظيفة تهيئة المعلومات حول أداة تحميل التشغيل ، وظيفة كتابة رأس ELF ووظيفة كتابة رأس البرنامج.

تهيئة معلومات أداة تحميل التشغيل


يتم تضمين رمز الجهاز لودر في باكر قابل للتنفيذ باستخدام رمز بسيط أدناه:

 .data .globl _loader_begin .globl _loader_end _loader_begin: .incbin "loader" _loader_end: 

لتحديد عنوانها في الذاكرة ، يتم التصريح عن المتغيرات التالية في ملف main.c:

 extern void* _loader_begin; extern void* _loader_end; 

بعد ذلك ، ضع في الاعتبار وظيفة init_loader. أولاً ، يتم قراءة القيم التالية بالتسلسل: إزاحة نقطة الإدخال من بداية أداة تحميل التشغيل (إدخال_التوفير) ، إزاحة حجم ملف ELF المحزوم من بداية أداة تحميل الإقلاع (payload_size_patch_offset) ، إزاحة القيمة الأولية للمولد للمفتاح من بداية أداة تحميل الإقلاع (key_seed_patch) التهيئة من بداية أداة تحميل التشغيل (iv_seed_patch_offset). بعد ذلك ، تتم إضافة عنوان أداة التحميل إلى القيم الثلاث الأخيرة ، لذا عند استبدال مؤشرات التخصيص وإسناد القيم إليها ، سنستبدل الأصفار المعينة في مرحلة التخطيط (QUAD (0)) بالقيم التي نحتاجها.

 void init_loader(loader_t *l) { void *loader_begin = (void*)&_loader_begin; l->entry_offset = *(size_t*)loader_begin; loader_begin += sizeof(size_t); l->payload_size_patch_offset = *(void**)loader_begin; loader_begin += sizeof(void*); l->key_seed_pacth_offset = *(void**)loader_begin; loader_begin += sizeof(void*); l->iv_seed_patch_offset = *(void**)loader_begin; loader_begin += sizeof(void*); l->payload_size_patch_offset = (size_t)l->payload_size_patch_offset + loader_begin; l->key_seed_pacth_offset = (size_t)l->key_seed_pacth_offset + loader_begin; l->iv_seed_patch_offset = (size_t)l->iv_seed_patch_offset + loader_begin; l->loader_begin = loader_begin; l->loader_size = (void*)&_loader_end - loader_begin; } 


write_elf_ehdr


 void write_elf_ehdr(int fd, loader_t *loader) { //  ELF  Elf64_Ehdr ehdr; memset(ehdr.e_ident, 0, sizeof(ehdr.e_ident)); memcpy(ehdr.e_ident, ELFMAG, SELFMAG); ehdr.e_ident[EI_CLASS] = ELFCLASS64; ehdr.e_ident[EI_DATA] = ELFDATA2LSB; ehdr.e_ident[EI_VERSION] = EV_CURRENT; ehdr.e_ident[EI_OSABI] = ELFOSABI_NONE; ehdr.e_type = ET_DYN; ehdr.e_machine = EM_X86_64; ehdr.e_version = EV_CURRENT; ehdr.e_entry = sizeof(Elf64_Ehdr) + sizeof(Elf64_Phdr) + loader->entry_offset; ehdr.e_phoff = sizeof(Elf64_Ehdr); ehdr.e_shoff = 0; ehdr.e_flags = 0; ehdr.e_ehsize = sizeof(Elf64_Ehdr); ehdr.e_phentsize = sizeof(Elf64_Phdr); ehdr.e_phnum = 1; ehdr.e_shentsize = sizeof(Elf64_Shdr); ehdr.e_shnum = 0; ehdr.e_shstrndx = 0; write(fd, &ehdr, sizeof(ehdr)); //     return 0; } 

هنا يحدث التهيئة القياسية لرأس ELF وكتابتها اللاحقة إلى ملف ، الشيء الوحيد الذي يجب الانتباه إليه هو حقيقة أنه في ملفات ET_DYN ELF ، لا يتضمن الجزء الموصوف في رأس البرنامج الأول فقط التعليمات البرمجية القابلة للتنفيذ ، ولكن أيضًا رأس ELF وجميع الرؤوس برنامج. لذلك ، يجب أن يكون إزاحته من البداية مساوياً للصفر ، ويجب أن يكون الحجم هو مجموع حجم رأس ELF ، وجميع رؤوس البرنامج والرمز القابل للتنفيذ ، ويتم تحديد نقطة الإدخال على أنها مجموع حجم رأس ELF ، وحجم كل رؤوس البرامج والإزاحة من بداية الكود القابل للتنفيذ.

write_elf_phdr


 void write_elf_phdr(int fd, loader_t *loader, size_t payload_size) { //    Elf64_Phdr phdr; phdr.p_type = PT_LOAD; phdr.p_offset = 0; phdr.p_vaddr = 0; phdr.p_paddr = 0; phdr.p_filesz = sizeof(Elf64_Ehdr) + sizeof(Elf64_Phdr) + loader->loader_size + payload_size + AES_ENTROPY_BUFSIZE; phdr.p_memsz = phdr.p_filesz; phdr.p_flags = PF_R | PF_W | PF_X; phdr.p_align = 0x1000; write(fd, &phdr, sizeof(phdr)); //      } 

هنا ، تتم تهيئة رأس البرنامج ثم كتابته إلى ملف. يجب الانتباه إلى الإزاحة نسبة إلى بداية الملف وحجم القطعة الموصوفة في رأس البرنامج. كما هو موضح في الفقرة السابقة ، لا يتضمن الجزء الموصوف في هذا الرأس فقط التعليمات البرمجية القابلة للتنفيذ ، ولكن أيضًا رأس ELF ورأس البرنامج. نحن أيضًا نجعل الجزء الذي يحتوي على رمز قابل للتنفيذ متاحًا للكتابة ، ويرجع ذلك إلى أن تطبيق AES المستخدم في أداة تحميل التشغيل يقوم بتشفير البيانات وفك تشفيرها.

بعض الحقائق عن عمل باكر


أثناء الاختبار ، لوحظ أن البرامج المترجمة بشكل ثابت مع glibc تذهب إلى segfault عند بدء التشغيل ، بناءً على هذه التعليمات:

  movq٪ fs: 0x28 ،٪ rax 

لم أتمكن من معرفة سبب حدوث ذلك ، سأكون سعيدًا إذا كنت تشارك المعلومات حول هذا الموضوع. بدلاً من glibc ، يمكنك استخدام musl-libc ، كل شيء يعمل معه دون فشل. أيضًا ، تم اختبار الرازم مع برامج golang المترجمة بشكل ثابت ، على سبيل المثال ، خادم http. للحوادث الثابتة الكاملة لبرامج golang ، يجب استخدام الأعلام التالية:

  CGO_ENABLED = 0 go build -a -ldflags '-extldflags "-static"'. 

آخر شيء تم اختباره بواسطة packer كان ملفات ET_DYN ELF بدون رابط ديناميكي. صحيح ، عند العمل مع هذه الملفات ، قد تفشل وظيفة elf_load_addr. في الممارسة العملية ، يمكن قصها من أداة تحميل التشغيل واستخدام عنوان ثابت ، على سبيل المثال 0x10000.

استنتاج


من الواضح أن هذا المعبئ ليس من المنطقي استخدامه للغرض المقصود منه ، لأن الملفات المحمية به يتم فك تشفيرها بسهولة. كان الهدف من هذا المشروع هو إتقان العمل بشكل أفضل مع ملفات ELF ، وممارسة إنشاءها ، بالإضافة إلى التحضير لإنشاء أداة تعبئة كاملة.

Source: https://habr.com/ru/post/ar483368/


All Articles