Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't add a new segment in a non-pie dynamic ELF #143

Closed
sar5430 opened this issue Feb 26, 2018 · 12 comments
Closed

Can't add a new segment in a non-pie dynamic ELF #143

sar5430 opened this issue Feb 26, 2018 · 12 comments
Assignees
Milestone

Comments

@sar5430
Copy link

sar5430 commented Feb 26, 2018

This issue is closely related to #98
Hi ! I'm doing some test using LIEF and a simple hello world.

#include <stdio.h>

int main(){
    puts("hello world");
}

Compiled without pie

gcc -m32 -fno-pie -no-pie hello.c -o hello

Replacing the NOTE segment with an updated segment is fully working, the binary does print "hello world" (I used the code you provided here #98 (comment))
However, if I try to add a new LOAD segment within this script

import lief

binary = lief.parse('hello')

segment = lief.ELF.Segment()

segment           = lief.ELF.Segment()
segment.type      = lief.ELF.SEGMENT_TYPES.LOAD
segment.content   = [0x41, 0x41, 0x41, 0x41]
segment.alignment = 0x1000
#segment = binary.replace(segment, binary[lief.ELF.SEGMENT_TYPES.NOTE])
binary.add(segment)

binary.write('hello_lief')

The generated binary is not properly working

s4r@temple ~$ ./hello_lief
Inconsistency detected by ld.so: rtld.c: 1263: dl_main: Assertion `GL(dl_rtld_map).l_libname' failed!

It looks like it broke something in dynamic linking

@romainthomas
Copy link
Member

Hi @0xs4r
It's an interesting error and it's seems to be specific to 32-bits executables.

By looking at the source code of the loader:
https://github.com/bminor/glibc/blob/1c9a5c270d8b66f30dcfaf1cb2d6cf39d3e18369/elf/rtld.c#L1262
It looks like that themselves don't know how it can happens...

I'll investigate this error this week

@romainthomas romainthomas self-assigned this Feb 26, 2018
@romainthomas romainthomas added this to the 0.9.0 milestone Feb 26, 2018
@romainthomas
Copy link
Member

Very weird: if you do /lib/ld-linux.so.2 ./hello_lief it works ...

@sar5430
Copy link
Author

sar5430 commented Feb 26, 2018

Indeed it works for me too

@sar5430
Copy link
Author

sar5430 commented Feb 26, 2018

It look really weird, I can't spot where this come from within the glibc source code right now but i'ii try to find out why this is happening. Anyway, if it works with
/lib/ld-linux.so.2 ./hello_lief, it should work by simply launching the new executable. Could it be an unwanted behavior from the glibc ?

@romainthomas
Copy link
Member

When we insert a segment in a non-pie executable, we move the program headers at the end of the binary.

In the hello_lief readelf gives the following output:

 readelf -hl ./hello_lief
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x80483b0
  Start of program headers:          7229 (bytes into file)
  Start of section headers:          14781 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         10
  Size of section headers:           40 (bytes)
  Number of section headers:         31
  Section header string table index: 30

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x001c3d 0x08049c3d 0x08049c3d 0x00140 0x00140 R E 0x4
  INTERP         0x000154 0x08048154 0x08048154 0x00047 0x00047 R   0x1
      [Requesting program interpreter: /usr/lib/ld.so]
  LOAD           0x000000 0x08048000 0x08048000 0x01d7d 0x01d7d R E 0x1000
  LOAD           0x000f04 0x08049f04 0x08049f04 0x00118 0x0011c RW  0x1000
  LOAD           0x002000 0x10002000 0x10002000 0x01000 0x01000     0x1000
  DYNAMIC        0x000f0c 0x08049f0c 0x08049f0c 0x000e8 0x000e8 RW  0x4
  NOTE           0x00019c 0x0804819c 0x0804819c 0x00044 0x00044 R   0x4
  GNU_EH_FRAME   0x00055c 0x0804855c 0x0804855c 0x00034 0x00034 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10
  GNU_RELRO      0x000f04 0x08049f04 0x08049f04 0x000fc 0x000fc 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 .rel.dyn .rel.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame 
   03     .init_array .fini_array .dynamic .got .got.plt .data .bss 
   04     
   05     .dynamic 
   06     .note.ABI-tag .note.gnu.build-id 
   07     .eh_frame_hdr 
   08     
   09     .init_array .fini_array .dynamic .got

The new phdr is located at the offset 0x001c3d and should be mapped at address 0x08049c3d but when I look at the bytes at this address it's empty:

ss2

Thus the loader iterate over an empty program headers.

As the program is running when launching with /lib/ld-linux.so.2 ./hello_lief is suspect that the issue comes from kernel (https://github.com/torvalds/linux/blob/v4.14/fs/binfmt_elf.c)

@sar5430
Copy link
Author

sar5430 commented Mar 2, 2018

How did you managed to print the empty program header in gdb? In my case, this memory address is not accessible, because it's not mapped.

Could this come from the fact that, in hello_lief, the second LOAD segment (.data, .bss, ...) overlap the first one ? The segment start at 0x08049f04 but the mapping start at 0x08049000 because of alignement. Here, this address is in the middle of the first LOAD segment.

This isn't happening in hello, because the first LOAD segment is shorter.

EDIT: Well, by manually modifying vaddr of the second LOAD segment, it looks like it solve our problem

s4r@temple ~$ readelf -l hello_lief2

Type de fichier ELF est EXEC (fichier exécutable)
Point d'entrée 0x8048380
Il y a 10 en-têtes de programme, débutant à l'adresse de décalage7230

En-têtes de programme :
  Type           Décalage Adr. vir.  Adr.phys.  T.Fich. T.Mém.  Fan Alignement
  PHDR           0x001c3e 0x08049c3e 0x08049c3e 0x00140 0x00140 R E 0x4
  INTERP         0x000154 0x08048154 0x08048154 0x00013 0x00013 R   0x1
      [Réquisition de l'interpréteur de programme: /lib/ld-linux.so.2]
  LOAD           0x000000 0x08048000 0x08048000 0x01d7e 0x01d7e R E 0x1000
  LOAD           0x000f04 0x08059f04 0x08059f04 0x00118 0x0011c RW  0x1000
  LOAD           0x002000 0x10002000 0x10002000 0x01000 0x01000     0x1000
  DYNAMIC        0x000f0c 0x08059f0c 0x08059f0c 0x000e8 0x000e8 RW  0x4
  NOTE           0x000168 0x08048168 0x08048168 0x00044 0x00044 R   0x4
  GNU_EH_FRAME   0x00053c 0x0804853c 0x0804853c 0x00034 0x00034 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10
  GNU_RELRO      0x000f04 0x08059f04 0x08059f04 0x000fc 0x000fc R   0x1

 Correspondance section/segment :
  Sections de segment...
   00
   01     .interp
   02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame
   03
   04
   05
   06     .note.ABI-tag .note.gnu.build-id
   07     .eh_frame_hdr
   08
   09

s4r@temple ~$ strace ./hello_lief2
execve("./hello_lief2", ["./hello_lief2"], 0x7ffcb60f9260 /* 22 vars */) = 0
strace: [ Process PID=26841 runs in 32 bit mode. ]
brk(NULL)                               = 0x101e3000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=89065, ...}) = 0
mmap2(NULL, 89065, PROT_READ, MAP_PRIVATE, 3, 0) = 0xf7f98000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib32/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\3\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\20\211\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=2139792, ...}) = 0
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xf7f96000
mmap2(NULL, 1911236, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xf7dc3000
mprotect(0xf7f8f000, 4096, PROT_NONE)   = 0
mmap2(0xf7f90000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1cc000) = 0xf7f90000
mmap2(0xf7f93000, 10692, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xf7f93000
close(3)                                = 0
set_thread_area({entry_number=-1, base_addr=0xf7f96f80, limit=0x0fffff, seg_32bit=1, contents=0, read_exec_only=0, limit_in_pages=1, seg_not_present=0, useable=1}) = 0 (entry_number=12)
mprotect(0xf7f90000, 8192, PROT_READ)   = 0
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x804a004} ---
+++ killed by SIGSEGV (core dumped) +++
Erreur de segmentation (core dumped)

The program is still segfaulting because I did not update the content of .dynamic section, but it looks like it's well loaded.

@romainthomas
Copy link
Member

Hi,
To debug the program I compiled the the ld-linux.so and set a breakpoint in the source code.
Indeed it looks like a page alignment issue, I will see how to deal with that in LIEF

@lmwhite
Copy link

lmwhite commented Oct 24, 2018

Has any progress been made with this issue? I just encountered this also when attempting to use LIEF to add segments to a 32 bit ELF file.

@sar5430
Copy link
Author

sar5430 commented Oct 24, 2018

At the moment it seems that the problem is still here. However you can update the NOTE segment with your updated section.

@fcremo
Copy link

fcremo commented Jul 5, 2019

Hi, has there been any progress on this issue, or could you give me some pointers on how to fix this?

From what I've understood the underlying problem is that the kernel does the following:

NEW_AUX_ENT(AT_PHDR, load_addr + exec->e_phoff);

and I guess that AUX entry is used by the loader to locate the PHDR.

This is a problem when the PHDR is moved at the end of the binary, as its load address won't be load_addr + exec->e_phoff anymore.

Is this correct in your opinion?
Currently I'm working around the problem by manully:

  • padding the binary to align to 0x1000 (not sure it's necessary)
  • adding the new extended PHDR segment table, with a LOAD entry mapping the new PHDR so that it is at load_addr + exec->e_phoff
  • modifying the PHDR segment offset in the ELF header to point to the new PHDR segment
  • adding the new segments

There is a problem if the original binary maps a big .bss for instance, as I would need to pad the binary to account for that, but I haven't found a better way to do it.
Substituting the notes segment is not an option for me unfortunately.

--
PS: LIEF is amazing!

@romainthomas
Copy link
Member

Hello @fcremo
I did not investigate this issue but if you have a working fix, I could integrate it.

@romainthomas
Copy link
Member

Should be fixed now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants