Stats

  • Total des pages vues: 38541
  • Pages vues aujourd'hui: 197
  • Visiteurs connectés: 2
  • Nombre de visiteurs: 23690

Making ELF packer for fun and chocapicz (part 2)

Written by aaSSfxxx - 17 may 2013

As promised, here is the second article about my ELF packer.
Here, I'll talk about dynamically-linked ELF (i.e. which has dependencies to ".so" modules), which is more tricky than the "basic" packer I showed before. The code is still NASM, and still under 32bit (feel free to rewrite the code to support 64-bit architecture ;))

Loading ld-linux

The first step needed here is loading the dynamic linker itself (because our asm program is standalone, so we don't have dynamic linker loaded at this time). To know which dynamic linker we have to use, let's read the filename contained in the "PT_INTERP" section.
Once the linker filename got, we have to map it into memory (and here, we can do it directly by opening the file and using the file descriptor returned, no need to "emulate" mapping). But, we have to take care of the "bss" section, which has to be zeroed after mapping of data section (the bss section is located at the end of file, according to Linux kernel code). I made a function which maps ld-linux.so into memory (quick and dirty code), and here is its code:

Code ASM :
  1. ; This function (shortened to see the most interesting part) loads the interpreter into memory space
  2. load_interp:
  3.     push ebp
  4.     mov ebp, esp
  5.     sub esp, 1ch
  6.     ; Zeroes some variables
  7.     xor edx, edx
  8.     mov [ebp-10h], edx
  9.     mov [ebp-18h], edx
  10.     mov [ebp-1ch], edx
  11.     ; Get a FD to the dynamic loader
  12.     xor ecx, ecx
  13.     mov ebx, [ebp+8]
  14.     mov eax, SYS_OPEN
  15.     int 0x80
  16.     test eax, eax
  17.     jz exit
  18.     mov [ebp-4], eax
  19.     
  20.     [...]
  21.  
  22.     ; Start our job
  23.     .loop:
  24.         cmp dword[ebx+elf32_phdr.p_type], PT_LOAD
  25.         jnz .next
  26.             push ebx
  27.             
  28.             ;; push file offset
  29.             
  30.             mov eax, dword [ebx+elf32_phdr.p_offset] ; file offset
  31.             ; remove page glitch to offset
  32.             mov edx, dword [ebx+elf32_phdr.p_vaddr]
  33.             and edx, 0xfff
  34.             sub eax, edx    
  35.             push eax ; push file offset
  36.             
  37.             ;; push file descriptor
  38.             
  39.             push dword [ebp-4] ; push fd
  40.             
  41.             ;; add "MAP_FIXED" flag if we already mapped something
  42.  
  43.             mov eax, MAP_PRIVATE | MAP_DENYWRITE ; flags
  44.             cmp dword[ebp-10h], 0
  45.             jz .no_fixed
  46.             or eax, MAP_FIXED
  47.             .no_fixed:
  48.             push eax ; push flag
  49.             
  50.             ;; get read/write flags
  51.             
  52.             push ebx
  53.             mov ebx, [ebx+elf32_phdr.p_flags]
  54.             call get_map_prot
  55.             pop ebx
  56.             push eax ; push flags
  57.             
  58.             ;; push size of mapping
  59.             
  60.             mov eax, dword [ebx+elf32_phdr.p_memsz]
  61.             ; add remainder
  62.             mov edx, dword [ebx+elf32_phdr.p_vaddr]
  63.             and edx, 0fffh
  64.             add eax, edx
  65.             ; round it up to 1 page
  66.             add eax, 1000h
  67.             and eax, 0fffff000h
  68.             push eax
  69.             
  70.             ;; get RVA where to map it and add imagebase to it
  71.             
  72.             mov eax, dword [ebx+elf32_phdr.p_vaddr]
  73.             add eax, [ebp-10h]
  74.             and eax, 0fffff000h
  75.             push eax ; voffset
  76.             
  77.             mov ebx, esp
  78.             mov eax, SYS_MMAP
  79.             int 0x80
  80.             add esp, 24
  81.             
  82.             pop ebx
  83.             
  84.             ;save image base if not stored
  85.             cmp dword[ebp-10h], 0
  86.             jnz .nosaved
  87.             mov [ebp-10h], eax
  88.             mov ebx, eax
  89.             add ebx, dword [ebx+elf32_hdr.e_phoff]
  90.             .nosaved:
  91.             
  92.             ;; Save elf_bss and map_bss as Linux kernel does
  93.             
  94.             ;save elf_bss if greater than stocked
  95.             mov edx, [ebp-10h]
  96.             add edx, dword[ebx+elf32_phdr.p_filesz]
  97.             add edx, dword[ebx+elf32_phdr.p_vaddr]
  98.             cmp dword[ebp-18h], edx
  99.             ja .no_elf_bss
  100.                 mov [ebp-18h], edx
  101.             .no_elf_bss:
  102.             
  103.             ;save map_bss if greater than stocked
  104.             mov edx, [ebp-10h]
  105.             add edx, dword[ebx+elf32_phdr.p_memsz]
  106.             add edx, dword[ebx+elf32_phdr.p_vaddr]
  107.             cmp dword[ebp-1ch], edx
  108.             ja .no_map_bss
  109.                 mov [ebp-1ch], edx
  110.             .no_map_bss:
  111.             
  112.         .next:
  113.         add ebx, 32
  114.         dec ecx
  115.     jnz .loop
  116.     ; Now, fill of zeros padding between last_bss and end of file
  117.     mov ecx, [ebp-18h] ; grab elf_bss
  118.     mov edi, ecx
  119.     add ecx, 1000h
  120.     and ecx, 0fffff000h ; align it to one page
  121.     sub ecx, edi
  122.     xor eax, eax
  123.     rep stosb
  124.     mov eax, [ebp-14h]
  125.     add eax, [ebp-10h]
  126.     leave
  127.     ret 4
  128.     
  129. get_map_prot:
  130.     xor eax, eax
  131.     test ebx, PF_R
  132.     jz .test_w
  133.     or eax, PROT_READ
  134.     .test_w:
  135.     test ebx, PF_W
  136.     jz .test_x
  137.     or eax, PROT_WRITE
  138.     .test_x:
  139.     test ebx, PF_X
  140.     jz .next
  141.     or eax, PROT_EXEC
  142.     .next:
  143.     ret
  144.  

This function returns the entry point of the dynamic linker, in order to be called after, and the function "get_map_prot" returns permissions according to section flags in ELF.
But, even if we map ld-linux, our job is not done here, there is extra initialization to do before.

Auxiliary vectors

When Linux kernel loads a executable file, it puts some stuff in the stack after envp (and of course, argc and argv). This zone is called "auxiliary vectors", because of its name "auxp" in ld-linux code. So, to have something working properly, we'll have to modify some of this auxiliary vectors, but we'll have to find them before. To do this, here is the code:

Code ASM :
  1. ; Finds the auxp array (after endp)
  2. find_auxp_array:
  3.   lea eax, [ebp+8] ; ptr to argv
  4.   mov ecx, [ebp+4]
  5.   lea eax, [eax + 4*ecx] ;ptr to envp
  6.   .loop:
  7.     add eax, 4
  8.     cmp dword[eax], 0
  9.     jnz .loop
  10.     add eax, 4
  11.   ret
  12.  

This code has to be called in the "main" function which has to have a stack frame, because it uses stack frame of main function to grab argument pointer. Then it lookups envp pointer array in odrer to locate auxp array.

An auxp element is a structure which is:

Code ASM :
  1. typedef struct
  2. {
  3.   int a_type;                   /* Entry type */
  4.   union
  5.     {
  6.       long int a_val;           /* Integer value */
  7.       void *a_ptr;              /* Pointer value */
  8.       void (*a_fcn) (void);     /* Function pointer value */
  9.     } a_un;
  10. } Elf32_auxv_t;
  11.  

Each auxiliary vector has an ID which specifies the type of data contained in the vector. For us, we'll need to update stuff related to ELF header, and more especially related to program header table (and also the entry point), because ld-linux uses these vectors. So, we'll need to modify AT_PHNUM and AT_ENTRY vectors (we don't change AT_PHDR, we assume program header is still at the same place), which is done here:

Code ASM :
  1. call find_auxp_array
  2.         ; change data in auxp vectors
  3.         .auxp_loop:
  4.             cmp dword[eax], 0
  5.                 jz .auxp_end ; jump if no auxp vectors
  6.             cmp dword[eax], AT_PHNUM
  7.             jnz .no_phnum
  8.                 movzx ebx, word[edx+elf32_hdr.e_phnum]
  9.                 mov dword[eax+4], ebx
  10.             .no_phnum:
  11.             cmp dword[eax], AT_ENTRY
  12.             jnz .no_entry
  13.                 mov ebx, [edx+elf32_hdr.e_entry]
  14.                 mov dword[eax+4], ebx
  15.             .no_entry:
  16.             add eax, 8
  17.             jmp .auxp_loop
  18.         .auxp_end:
  19.         ; program has PT_INTERP, map it into mem
  20.         push dword[ebp-dynamic]
  21.         call load_interp
  22.  
Then, once all these initializations done, enjoy ur ELF packer :].

Source code of this can be found at this address.

Classified in : Hacking & Programming - Tags : none

wednesday 29 may 2013 @ 11:23 Eddy said : #1

Avatar GravatarPourquoi tout coder en ASM ?

wednesday 29 may 2013 @ 11:30 aaSSfxxx said : #2

Avatar Gravatar@Eddy : Parce que l'asm c'est fun ? :þ
Plus sérieusement, pour que le truc fonctionne, on doit avoir un binaire "standalone" (sinon ça charge ld-linux, résout les symboles, tout ça et ça complique énormément la tâche après), et l'asm permet de faire ça assez facilement ;)

thursday 25 july 2013 @ 05:34 Rui said : #3

Avatar Gravatarseems the code link was down, any possible to get it back? thanks for sharing :)

friday 26 july 2013 @ 16:22 aaSSfxxx said : #4

Avatar Gravatar@Rui :
The code is up, maybe the hosting where my repository is hosted was down when you tried to download the code ;)

Comments are closed.